# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1634065486 -7200
#      Tue Oct 12 21:04:46 2021 +0200
# Branch heptapod
# Node ID 165749700fcf91aa0aab87370426464519421c81
# Parent  81a3afe35a6726ba827e5151d0191d18e734168a
HEPTAPOD_NO_GIT: carrying over from internal API response

As with Workhorse and direct subprocess calls, we'll need
this flag, whose effect on py-heptapod is to prevent Git
export entirely in the native case.

diff --git a/internal/command/hg/hg.go b/internal/command/hg/hg.go
--- a/internal/command/hg/hg.go
+++ b/internal/command/hg/hg.go
@@ -80,6 +80,7 @@
 		"HEPTAPOD_REPOSITORY_USAGE=" + strings.Split(response.Repo, "-")[0],
 		// FormatBool output is "true" or "false", understood by hg config
 		"HEPTAPOD_HG_NATIVE=" + strconv.FormatBool(response.HgNative),
+		"HEPTAPOD_NO_GIT=" + strconv.FormatBool(response.NoGit),
 	}...)
 	if c.Config.Mercurial.ModulePolicy != "" {
 		env = append(env, "HGMODULEPOLICY=" + c.Config.Mercurial.ModulePolicy)
diff --git a/internal/command/hg/hg_test.go b/internal/command/hg/hg_test.go
--- a/internal/command/hg/hg_test.go
+++ b/internal/command/hg/hg_test.go
@@ -72,7 +72,9 @@
 func TestExecuteSuccessfully(t *testing.T) {
 	type testcase struct {
 		native_input    *bool
+		no_git_input    *bool
 		native_expected string
+		no_git_expected string
 		module_policy   string
 	}
 
@@ -81,14 +83,17 @@
 	yes := true;
 
 	testcases := []testcase{
-		{native_input: nil, native_expected: "false", module_policy: "c"},
-		{native_input: &no, native_expected: "false", module_policy: ""},
-		{native_input: &yes, native_expected: "true"},
+		{native_input: nil, native_expected: "false", no_git_expected: "false", module_policy: "c"},
+		{native_input: &no, native_expected: "false", no_git_expected: "false", module_policy: ""},
+		{native_input: &yes, native_expected: "true", no_git_expected: "false"},
+		{native_input: nil, no_git_input: &yes, native_expected: "false", no_git_expected: "true", module_policy: "c"},
+		{native_input: &no, no_git_input: &yes, native_expected: "false", no_git_expected: "true", module_policy: ""},
+		{native_input: &yes, native_expected: "true", no_git_input: &yes, no_git_expected: "true"},
 	}
 
 	for _, tc := range testcases {
 
-		requests := requesthandlers.BuildHgApiSuccessHandlers(t, tc.native_input)
+		requests := requesthandlers.BuildHgApiSuccessHandlers(t, tc.native_input, tc.no_git_input)
 		url := testserver.StartHttpServer(t, requests)
 
 		output := &bytes.Buffer{}
@@ -128,6 +133,7 @@
 		require.Equal(t, "awesome-proj", fake.Env["HEPTAPOD_PROJECT_PATH"])
 		require.Equal(t, "my/group", fake.Env["HEPTAPOD_PROJECT_NAMESPACE_FULL_PATH"])
 		require.Equal(t, tc.native_expected, fake.Env["HEPTAPOD_HG_NATIVE"])
+		require.Equal(t, tc.no_git_expected, fake.Env["HEPTAPOD_NO_GIT"])
 		require.Equal(t, "/usr/share/shipped.hgrc:/etc/main.hgrc", fake.Env["HGRCPATH"])
 		require.Equal(t, tc.module_policy, fake.Env["HGMODULEPOLICY"])
 	}
@@ -135,7 +141,7 @@
 
 func TestIgnoreHarmfulClientArgs(t *testing.T) {
 
-	requests := requesthandlers.BuildHgApiSuccessHandlers(t, nil)
+	requests := requesthandlers.BuildHgApiSuccessHandlers(t, nil, nil)
 	url := testserver.StartHttpServer(t, requests)
 
 	output := &bytes.Buffer{}
diff --git a/internal/gitlabnet/accessverifier/hg_client.go b/internal/gitlabnet/accessverifier/hg_client.go
--- a/internal/gitlabnet/accessverifier/hg_client.go
+++ b/internal/gitlabnet/accessverifier/hg_client.go
@@ -33,6 +33,7 @@
 	Email      string `json:"gl_email"`
 	ProjectId  int    `json:"project_id"`
 	HgNative   bool   `json:"hg_native"`
+	NoGit      bool   `json:"no_git"`
 	StatusCode int
 }
 
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -50,7 +50,7 @@
 	return requests
 }
 
-func BuildHgApiSuccessHandlers(t *testing.T, hg_native *bool) []testserver.TestRequestHandler {
+func BuildHgApiSuccessHandlers(t *testing.T, hg_native *bool, no_git *bool) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/hg_access",
@@ -68,6 +68,9 @@
 				if (hg_native != nil) {
 					body["hg_native"] = *hg_native
 				}
+				if (no_git != nil) {
+					body["no_git"] = *no_git
+				}
 				w.WriteHeader(http.StatusOK)
 				require.NoError(t, json.NewEncoder(w).Encode(body))
 			},
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1635200949 -7200
#      Tue Oct 26 00:29:09 2021 +0200
# Branch heptapod
# Node ID 0eee7fea773f012168ff4499c06a94f2df12ffc7
# Parent  165749700fcf91aa0aab87370426464519421c81
# Parent  23b11deb9b862507063f1da21be251b61a7d4fb4
Merged heptapod-stable branch into heptapod

Carries only CI rules changes

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -31,5 +31,6 @@
 3b2b3737c7a656cf2383fff51d57f51573e9b981 heptapod-13.16.0
 4def2dd0132fde7ae4fd57ea21d402860dcd3612 heptapod-13.17.0
 ed9dd657a0ab833fd60fe809385f2b73947d0819 heptapod-13.18.0
+3cfed32ac2038975b533352ff0f4839aa2a0a319 heptapod-13.18.1
 aefbb5e3eac5411fc526a88d3df1762ecbdc2e76 heptapod-13.19.0
 b84ce63181d5c0d07585be867dbe957eb6d35348 heptapod-13.19.1
diff --git a/heptapod-ci.yml b/heptapod-ci.yml
--- a/heptapod-ci.yml
+++ b/heptapod-ci.yml
@@ -6,9 +6,11 @@
     # For merge requests, create a pipeline.
     - if: '$CI_MERGE_REQUEST_IID'
     # For main development branches, create a pipeline (this includes on schedules, pushes, merges, etc.).
-    - if: '"$CI_COMMIT_HG_BRANCH" =~ "/^heptapod-.*/" && "$CI_COMMIT_HG_TOPIC" == ""'
+    - if: '"$CI_COMMIT_HG_BRANCH" =~ /^heptapod-/ && "$CI_COMMIT_HG_TOPIC" == ""'
     # For tags, create a pipeline.
     - if: '$CI_COMMIT_TAG'
+    # Always create a pipeline if manually requested
+    - if: '$CI_PIPELINE_SOURCE == "web"'
 
 default:
   image: golang:1.14
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1635201049 -7200
#      Tue Oct 26 00:30:49 2021 +0200
# Branch heptapod
# Node ID f593b81abfe52ca2083440f3f470dce3d13079da
# Parent  0eee7fea773f012168ff4499c06a94f2df12ffc7
Updated version files for release

diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -9,6 +9,11 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 13.19.2
+
+- Added support for the new `NO_GIT` flag, telling Mercurial not to
+  use hg-git
+
 ## 13.19.1
 
 - No actual changes
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-13.19.1
+13.19.2
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1635201056 -7200
#      Tue Oct 26 00:30:56 2021 +0200
# Branch heptapod
# Node ID 837e1b491f17f9d8f8e2c0e5bb5519de76b6c4fb
# Parent  f593b81abfe52ca2083440f3f470dce3d13079da
Added tag heptapod-13.19.2 for changeset f593b81abfe5

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -34,3 +34,4 @@
 3cfed32ac2038975b533352ff0f4839aa2a0a319 heptapod-13.18.1
 aefbb5e3eac5411fc526a88d3df1762ecbdc2e76 heptapod-13.19.0
 b84ce63181d5c0d07585be867dbe957eb6d35348 heptapod-13.19.1
+f593b81abfe52ca2083440f3f470dce3d13079da heptapod-13.19.2
# HG changeset patch
# User Brad Sevy <bsevy@gitlab.com>
# Date 1623066789 0
#      Mon Jun 07 11:53:09 2021 +0000
# Node ID ca0dc0a8719c2f677f66f206650511349779dfee
# Parent  f0156728df1a322c23379d1e9ebf3fdc952df64a
"Limits" to "Limit" on line 10 to align tenses

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
 
 When you access the GitLab server over SSH then GitLab Shell will:
 
-1. Limits you to predefined git commands (git push, git pull).
+1. Limit you to predefined git commands (git push, git pull).
 1. Call the GitLab Rails API to check if you are authorized, and what Gitaly server your repository is on
 1. Copy data back and forth between the SSH client and the Gitaly server
 
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1623113054 0
#      Tue Jun 08 00:44:14 2021 +0000
# Node ID 7719f6152eb70048f2b92a6fb50e45bc5769eba4
# Parent  f0156728df1a322c23379d1e9ebf3fdc952df64a
# Parent  ca0dc0a8719c2f677f66f206650511349779dfee
Merge branch 'brad-tense-correction' into 'main'

"Limits" to "Limit" on line 10 to align tenses

See merge request gitlab-org/gitlab-shell!483

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
 
 When you access the GitLab server over SSH then GitLab Shell will:
 
-1. Limits you to predefined git commands (git push, git pull).
+1. Limit you to predefined git commands (git push, git pull).
 1. Call the GitLab Rails API to check if you are authorized, and what Gitaly server your repository is on
 1. Copy data back and forth between the SSH client and the Gitaly server
 
# HG changeset patch
# User Gary Holtz <gholtz@gitlab.com>
# Date 1624042629 18000
#      Fri Jun 18 13:57:09 2021 -0500
# Node ID 334265484289351f0118bffe439b252224a27caa
# Parent  7719f6152eb70048f2b92a6fb50e45bc5769eba4
Adding a UTC converter and test

diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -10,9 +10,21 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
+type UTCFormatter struct {
+	log.Formatter
+}
+
+func (u UTCFormatter) Format(e *log.Entry) ([]byte, error) {
+	e.Time = e.Time.UTC()
+
+	return u.Formatter.Format(e)
+}
+
 func configureLogFormat(cfg *config.Config) {
 	if cfg.LogFormat == "json" {
-		log.SetFormatter(&log.JSONFormatter{})
+		log.SetFormatter(UTCFormatter{&log.JSONFormatter{}})
+	} else {
+		log.SetFormatter(UTCFormatter{&log.TextFormatter{}})
 	}
 }
 
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -3,6 +3,7 @@
 import (
 	"io/ioutil"
 	"os"
+	"regexp"
 	"strings"
 	"testing"
 
@@ -44,3 +45,27 @@
 	Configure(&config)
 	log.Info("this is a test")
 }
+
+func TestLogInUTC(t *testing.T) {
+	tmpFile, err := ioutil.TempFile(os.TempDir(), "logtest-")
+	require.NoError(t, err)
+	defer tmpFile.Close()
+	defer os.Remove(tmpFile.Name())
+
+	config := config.Config{
+		LogFile:   tmpFile.Name(),
+		LogFormat: "json",
+	}
+
+	Configure(&config)
+	log.Info("this is a test")
+
+	data, err := ioutil.ReadFile(tmpFile.Name())
+	require.NoError(t, err)
+
+	utc := `[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z`
+	r, e := regexp.MatchString(utc, string(data))
+
+	require.NoError(t, e)
+	require.True(t, r)
+}
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1624274902 0
#      Mon Jun 21 11:28:22 2021 +0000
# Node ID e217294e7c0ea89fe37160a49b0d3ced586063ca
# Parent  7719f6152eb70048f2b92a6fb50e45bc5769eba4
# Parent  334265484289351f0118bffe439b252224a27caa
Merge branch '140-standardize-logging-timestamp-format' into 'main'

Standardize logging timestamp format

Closes #140

See merge request gitlab-org/gitlab-shell!485

diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -10,9 +10,21 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
+type UTCFormatter struct {
+	log.Formatter
+}
+
+func (u UTCFormatter) Format(e *log.Entry) ([]byte, error) {
+	e.Time = e.Time.UTC()
+
+	return u.Formatter.Format(e)
+}
+
 func configureLogFormat(cfg *config.Config) {
 	if cfg.LogFormat == "json" {
-		log.SetFormatter(&log.JSONFormatter{})
+		log.SetFormatter(UTCFormatter{&log.JSONFormatter{}})
+	} else {
+		log.SetFormatter(UTCFormatter{&log.TextFormatter{}})
 	}
 }
 
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -3,6 +3,7 @@
 import (
 	"io/ioutil"
 	"os"
+	"regexp"
 	"strings"
 	"testing"
 
@@ -44,3 +45,27 @@
 	Configure(&config)
 	log.Info("this is a test")
 }
+
+func TestLogInUTC(t *testing.T) {
+	tmpFile, err := ioutil.TempFile(os.TempDir(), "logtest-")
+	require.NoError(t, err)
+	defer tmpFile.Close()
+	defer os.Remove(tmpFile.Name())
+
+	config := config.Config{
+		LogFile:   tmpFile.Name(),
+		LogFormat: "json",
+	}
+
+	Configure(&config)
+	log.Info("this is a test")
+
+	data, err := ioutil.ReadFile(tmpFile.Name())
+	require.NoError(t, err)
+
+	utc := `[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z`
+	r, e := regexp.MatchString(utc, string(data))
+
+	require.NoError(t, e)
+	require.True(t, r)
+}
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1624439362 -10800
#      Wed Jun 23 12:09:22 2021 +0300
# Node ID e093b917055514501be4def3f666a4dc73c84937
# Parent  e217294e7c0ea89fe37160a49b0d3ced586063ca
Create PROCESS.md page with Security release process

diff --git a/PROCESS.md b/PROCESS.md
new file mode 100644
--- /dev/null
+++ b/PROCESS.md
@@ -0,0 +1,44 @@
+## Releasing a new version
+
+GitLab Shell is versioned by git tags, and the version used by the Rails
+application is stored in
+[`GITLAB_SHELL_VERSION`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_SHELL_VERSION).
+
+For each version, there is a raw version and a tag version:
+
+- The **raw version** is the version number. For instance, `15.2.8`.
+- The **tag version** is the raw version prefixed with `v`. For instance, `v15.2.8`.
+
+To release a new version of GitLab Shell and have that version available to the
+Rails application:
+
+1. Create a merge request to update the [`CHANGELOG`](CHANGELOG) with the
+   **tag version** and the [`VERSION`](VERSION) file with the **raw version**.
+2. Ask a maintainer to review and merge the merge request. If you're already a
+   maintainer, second maintainer review is not required.
+3. Add a new git tag with the **tag version**.
+4. Update `GITLAB_SHELL_VERSION` in the Rails application to the **raw
+   version**. (Note: this can be done as a separate MR to that, or in and MR
+   that will make use of the latest GitLab Shell changes.)
+
+## Security releases
+
+GitLab Shell is included in the packages we create for GitLab, and each version of GitLab specifies the version of GitLab Shell it uses in the `GITLAB_SHELL_VERSION` file, so security fixes in GitLab Shell are tightly coupled to the [GitLab security release](https://about.gitlab.com/handbook/engineering/workflow/#security-issues) workflow.
+
+For a security fix in GitLab Shell, two sets of merge requests are required:
+
+* The fix itself, in the `gitlab-org/security/gitlab-shell` repository and its backports to the previous versions of GitLab Shell
+* Merge requests to change the versions of GitLab Shell included in the GitLab security release, in the `gitlab-org/security/gitlab` repository
+
+The first step could be to create a merge request with a fix targeting `main` in `gitlab-org/security/gitlab-shell`. When the merge request is approved by maintainers, backports targeting previous 3 versions of GitLab Shell must be created. The stable branches for those versions may not exist, feel free to ask a maintainer to create ones. The stable branches must be created out of the GitLab Shell tags/version used by the 3 previous GitLab releases. In order to find out the GitLab Shell version that is used on a particular GitLab stable release, the following steps may be helpful:
+
+```shell
+git fetch security 13-9-stable-ee
+git show refs/remotes/security/13-9-stable-ee:GITLAB_SHELL_VERSION
+```
+
+These steps display the version that is used by `13.9` version of GitLab.
+
+Close to the GitLab security release, a maintainer should merge the fix and backports and cut all the necessary GitLab Shell versions. This allows bumping the `GITLAB_SHELL_VERSION` for `gitlab-org/security/gitlab`. The GitLab merge request will be handled by the general GitLab security release process.
+
+Once the security release is done, a GitLab Shell maintainer is responsible for syncing tags and `main` to the `gitlab-org/gitlab-shell` repository.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -78,28 +78,9 @@
 
 Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
 
-## Releasing a new version
-
-GitLab Shell is versioned by git tags, and the version used by the Rails
-application is stored in
-[`GITLAB_SHELL_VERSION`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_SHELL_VERSION).
-
-For each version, there is a raw version and a tag version:
-
-- The **raw version** is the version number. For instance, `15.2.8`.
-- The **tag version** is the raw version prefixed with `v`. For instance, `v15.2.8`.
+## Releasing
 
-To release a new version of GitLab Shell and have that version available to the
-Rails application:
-
-1. Create a merge request to update the [`CHANGELOG`](CHANGELOG) with the
-   **tag version** and the [`VERSION`](VERSION) file with the **raw version**.
-2. Ask a maintainer to review and merge the merge request. If you're already a
-   maintainer, second maintainer review is not required.
-3. Add a new git tag with the **tag version**.
-4. Update `GITLAB_SHELL_VERSION` in the Rails application to the **raw
-   version**. (Note: this can be done as a separate MR to that, or in and MR
-   that will make use of the latest GitLab Shell changes.)
+See [PROCESS.md](./PROCESS.md)
 
 ## Contributing
 
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1624960917 0
#      Tue Jun 29 10:01:57 2021 +0000
# Node ID 1520d1b058c03d38688d8eacd2c92966634f78aa
# Parent  e217294e7c0ea89fe37160a49b0d3ced586063ca
# Parent  e093b917055514501be4def3f666a4dc73c84937
Merge branch 'id-create-process-page' into 'main'

Create PROCESS.md page with Security release process

See merge request gitlab-org/gitlab-shell!488

diff --git a/PROCESS.md b/PROCESS.md
new file mode 100644
--- /dev/null
+++ b/PROCESS.md
@@ -0,0 +1,44 @@
+## Releasing a new version
+
+GitLab Shell is versioned by git tags, and the version used by the Rails
+application is stored in
+[`GITLAB_SHELL_VERSION`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_SHELL_VERSION).
+
+For each version, there is a raw version and a tag version:
+
+- The **raw version** is the version number. For instance, `15.2.8`.
+- The **tag version** is the raw version prefixed with `v`. For instance, `v15.2.8`.
+
+To release a new version of GitLab Shell and have that version available to the
+Rails application:
+
+1. Create a merge request to update the [`CHANGELOG`](CHANGELOG) with the
+   **tag version** and the [`VERSION`](VERSION) file with the **raw version**.
+2. Ask a maintainer to review and merge the merge request. If you're already a
+   maintainer, second maintainer review is not required.
+3. Add a new git tag with the **tag version**.
+4. Update `GITLAB_SHELL_VERSION` in the Rails application to the **raw
+   version**. (Note: this can be done as a separate MR to that, or in and MR
+   that will make use of the latest GitLab Shell changes.)
+
+## Security releases
+
+GitLab Shell is included in the packages we create for GitLab, and each version of GitLab specifies the version of GitLab Shell it uses in the `GITLAB_SHELL_VERSION` file, so security fixes in GitLab Shell are tightly coupled to the [GitLab security release](https://about.gitlab.com/handbook/engineering/workflow/#security-issues) workflow.
+
+For a security fix in GitLab Shell, two sets of merge requests are required:
+
+* The fix itself, in the `gitlab-org/security/gitlab-shell` repository and its backports to the previous versions of GitLab Shell
+* Merge requests to change the versions of GitLab Shell included in the GitLab security release, in the `gitlab-org/security/gitlab` repository
+
+The first step could be to create a merge request with a fix targeting `main` in `gitlab-org/security/gitlab-shell`. When the merge request is approved by maintainers, backports targeting previous 3 versions of GitLab Shell must be created. The stable branches for those versions may not exist, feel free to ask a maintainer to create ones. The stable branches must be created out of the GitLab Shell tags/version used by the 3 previous GitLab releases. In order to find out the GitLab Shell version that is used on a particular GitLab stable release, the following steps may be helpful:
+
+```shell
+git fetch security 13-9-stable-ee
+git show refs/remotes/security/13-9-stable-ee:GITLAB_SHELL_VERSION
+```
+
+These steps display the version that is used by `13.9` version of GitLab.
+
+Close to the GitLab security release, a maintainer should merge the fix and backports and cut all the necessary GitLab Shell versions. This allows bumping the `GITLAB_SHELL_VERSION` for `gitlab-org/security/gitlab`. The GitLab merge request will be handled by the general GitLab security release process.
+
+Once the security release is done, a GitLab Shell maintainer is responsible for syncing tags and `main` to the `gitlab-org/gitlab-shell` repository.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -78,28 +78,9 @@
 
 Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
 
-## Releasing a new version
-
-GitLab Shell is versioned by git tags, and the version used by the Rails
-application is stored in
-[`GITLAB_SHELL_VERSION`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_SHELL_VERSION).
-
-For each version, there is a raw version and a tag version:
-
-- The **raw version** is the version number. For instance, `15.2.8`.
-- The **tag version** is the raw version prefixed with `v`. For instance, `v15.2.8`.
+## Releasing
 
-To release a new version of GitLab Shell and have that version available to the
-Rails application:
-
-1. Create a merge request to update the [`CHANGELOG`](CHANGELOG) with the
-   **tag version** and the [`VERSION`](VERSION) file with the **raw version**.
-2. Ask a maintainer to review and merge the merge request. If you're already a
-   maintainer, second maintainer review is not required.
-3. Add a new git tag with the **tag version**.
-4. Update `GITLAB_SHELL_VERSION` in the Rails application to the **raw
-   version**. (Note: this can be done as a separate MR to that, or in and MR
-   that will make use of the latest GitLab Shell changes.)
+See [PROCESS.md](./PROCESS.md)
 
 ## Contributing
 
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1624979605 -3600
#      Tue Jun 29 16:13:25 2021 +0100
# Node ID 74ea2712640f2116b2c498ec137f155e186bb74c
# Parent  1520d1b058c03d38688d8eacd2c92966634f78aa
Fix a failing spec

When the shell environment includes SSH_CONNECTION, one spec fails as
the way we're stubbing the environment to the subprocess doesn't wipe
out the pre-existing variable. This commit changes how we do it so the
spec passes even in this environment.

diff --git a/spec/gitlab_shell_discover_spec.rb b/spec/gitlab_shell_discover_spec.rb
--- a/spec/gitlab_shell_discover_spec.rb
+++ b/spec/gitlab_shell_discover_spec.rb
@@ -108,7 +108,7 @@
     end
 
     it 'outputs "Only SSH allowed"' do
-      _, stderr, status = run!(["-c/usr/share/webapps/gitlab-shell/bin/gitlab-shell", "username-someuser"], env: {})
+      _, stderr, status = run!(["-c/usr/share/webapps/gitlab-shell/bin/gitlab-shell", "username-someuser"], env: {'SSH_CONNECTION' => ''})
 
       expect(stderr).to eq("Only SSH allowed\n")
       expect(status).not_to be_success
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1625017871 0
#      Wed Jun 30 01:51:11 2021 +0000
# Node ID 5c7ba8814ad7107e3b658abf36bea6df13ff95d7
# Parent  1520d1b058c03d38688d8eacd2c92966634f78aa
# Parent  74ea2712640f2116b2c498ec137f155e186bb74c
Merge branch 'fix-failing-spec' into 'main'

Fix a failing spec

See merge request gitlab-org/gitlab-shell!489

diff --git a/spec/gitlab_shell_discover_spec.rb b/spec/gitlab_shell_discover_spec.rb
--- a/spec/gitlab_shell_discover_spec.rb
+++ b/spec/gitlab_shell_discover_spec.rb
@@ -108,7 +108,7 @@
     end
 
     it 'outputs "Only SSH allowed"' do
-      _, stderr, status = run!(["-c/usr/share/webapps/gitlab-shell/bin/gitlab-shell", "username-someuser"], env: {})
+      _, stderr, status = run!(["-c/usr/share/webapps/gitlab-shell/bin/gitlab-shell", "username-someuser"], env: {'SSH_CONNECTION' => ''})
 
       expect(stderr).to eq("Only SSH allowed\n")
       expect(status).not_to be_success
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1625142408 -3600
#      Thu Jul 01 13:26:48 2021 +0100
# Node ID 9c412e078524c8bd52e8df66692ecd65cf891d9a
# Parent  1520d1b058c03d38688d8eacd2c92966634f78aa
Add a make install command

Changelog: added

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: validate verify verify_ruby verify_golang test test_ruby test_golang coverage coverage_golang setup _install build compile check clean
+.PHONY: validate verify verify_ruby verify_golang test test_ruby test_golang coverage coverage_golang setup _script_install build compile check clean install
 
 GO_SOURCES := $(shell find . -name '*.go')
 VERSION_STRING := $(shell git describe --match v* 2>/dev/null || awk '$0="v"$0' VERSION 2>/dev/null || echo unknown)
@@ -6,6 +6,8 @@
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)"
 
+PREFIX ?= /usr/local
+
 validate: verify test
 
 verify: verify_golang
@@ -30,9 +32,9 @@
 coverage_golang:
 	[ -f cover.out ] && go tool cover -func cover.out
 
-setup: _install bin/gitlab-shell
+setup: _script_install bin/gitlab-shell
 
-_install:
+_script_install:
 	bin/install
 
 build: bin/gitlab-shell
@@ -45,3 +47,12 @@
 
 clean:
 	rm -f bin/check bin/gitlab-shell bin/gitlab-shell-authorized-keys-check bin/gitlab-shell-authorized-principals-check bin/gitlab-sshd
+
+install: compile
+	mkdir -p $(DESTDIR)$(PREFIX)/bin/
+	install -m755 bin/check $(DESTDIR)$(PREFIX)/bin/check
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-keys-check
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-principals-check
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-sshd
+
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -34,16 +34,36 @@
 We follow the [Golang Release Policy](https://golang.org/doc/devel/release.html#policy)
 of supporting the current stable version and the previous two major versions.
 
-## Setup
-
-    make setup
-
 ## Check
 
 Checks if GitLab API access and redis via internal API can be reached:
 
     make check
 
+## Compile
+
+Builds the `gitlab-shell` binaries, placing them into `bin/`.
+
+    make compile
+
+## Install
+
+Builds the `gitlab-shell` binaries and installs them onto the filesystem. The
+default location is `/usr/local`, but can be controlled by use of the `PREFIX`
+and `DESTDIR` environment variables.
+
+    make install
+
+## Setup
+
+This command is intended for use when installing GitLab from source on a single
+machine. In addition to compiling the gitlab-shell binaries, it ensures that
+various paths on the filesystem exist with the correct permissions. Do not run
+it unless instructed to by your installation method documentation.
+
+    make setup
+
+
 ## Testing
 
 Run tests:
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1625144842 0
#      Thu Jul 01 13:07:22 2021 +0000
# Node ID d247e327a7980d24cc3ad1b344e51eb936955044
# Parent  5c7ba8814ad7107e3b658abf36bea6df13ff95d7
# Parent  9c412e078524c8bd52e8df66692ecd65cf891d9a
Merge branch '475-make-install' into 'main'

Add a make install command

Closes #475

See merge request gitlab-org/gitlab-shell!490

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: validate verify verify_ruby verify_golang test test_ruby test_golang coverage coverage_golang setup _install build compile check clean
+.PHONY: validate verify verify_ruby verify_golang test test_ruby test_golang coverage coverage_golang setup _script_install build compile check clean install
 
 GO_SOURCES := $(shell find . -name '*.go')
 VERSION_STRING := $(shell git describe --match v* 2>/dev/null || awk '$0="v"$0' VERSION 2>/dev/null || echo unknown)
@@ -6,6 +6,8 @@
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)"
 
+PREFIX ?= /usr/local
+
 validate: verify test
 
 verify: verify_golang
@@ -30,9 +32,9 @@
 coverage_golang:
 	[ -f cover.out ] && go tool cover -func cover.out
 
-setup: _install bin/gitlab-shell
+setup: _script_install bin/gitlab-shell
 
-_install:
+_script_install:
 	bin/install
 
 build: bin/gitlab-shell
@@ -45,3 +47,12 @@
 
 clean:
 	rm -f bin/check bin/gitlab-shell bin/gitlab-shell-authorized-keys-check bin/gitlab-shell-authorized-principals-check bin/gitlab-sshd
+
+install: compile
+	mkdir -p $(DESTDIR)$(PREFIX)/bin/
+	install -m755 bin/check $(DESTDIR)$(PREFIX)/bin/check
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-keys-check
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-principals-check
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-sshd
+
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -34,16 +34,36 @@
 We follow the [Golang Release Policy](https://golang.org/doc/devel/release.html#policy)
 of supporting the current stable version and the previous two major versions.
 
-## Setup
-
-    make setup
-
 ## Check
 
 Checks if GitLab API access and redis via internal API can be reached:
 
     make check
 
+## Compile
+
+Builds the `gitlab-shell` binaries, placing them into `bin/`.
+
+    make compile
+
+## Install
+
+Builds the `gitlab-shell` binaries and installs them onto the filesystem. The
+default location is `/usr/local`, but can be controlled by use of the `PREFIX`
+and `DESTDIR` environment variables.
+
+    make install
+
+## Setup
+
+This command is intended for use when installing GitLab from source on a single
+machine. In addition to compiling the gitlab-shell binaries, it ensures that
+various paths on the filesystem exist with the correct permissions. Do not run
+it unless instructed to by your installation method documentation.
+
+    make setup
+
+
 ## Testing
 
 Run tests:
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1625142471 -3600
#      Thu Jul 01 13:27:51 2021 +0100
# Node ID f1429b64347f88576e0c36b54e297252b7e5b8dc
# Parent  1520d1b058c03d38688d8eacd2c92966634f78aa
Remove bin/authorized_keys

We committed to removing this script in the 13.x release cycle, and it
was announced then, but we never actually got around to it.

Its functionality is completely replicated, in a safer manner, by the
bin/gitlab-shell-authorized-keys script.

Changelog: removed

diff --git a/bin/authorized_keys b/bin/authorized_keys
deleted file mode 100755
--- a/bin/authorized_keys
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/sh
-
-# Legacy script used for AuthorizedKeysCommand when configured without username.
-# Executes gitlab-shell-authorized-keys-check with "git" as expected and actual
-# username and with the passed key.
-#
-# TODO: Remove this in https://gitlab.com/gitlab-org/gitlab-shell/issues/209.
-
-$(dirname $0)/gitlab-shell-authorized-keys-check git git $1
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1625467858 0
#      Mon Jul 05 06:50:58 2021 +0000
# Node ID 0a2a210a4b1d0e5f335a6c35ac3ae3d9356d4399
# Parent  d247e327a7980d24cc3ad1b344e51eb936955044
# Parent  f1429b64347f88576e0c36b54e297252b7e5b8dc
Merge branch 'remove-bin-authorized-keys' into 'main'

Remove bin/authorized_keys

See merge request gitlab-org/gitlab-shell!491

diff --git a/bin/authorized_keys b/bin/authorized_keys
deleted file mode 100755
--- a/bin/authorized_keys
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/sh
-
-# Legacy script used for AuthorizedKeysCommand when configured without username.
-# Executes gitlab-shell-authorized-keys-check with "git" as expected and actual
-# username and with the passed key.
-#
-# TODO: Remove this in https://gitlab.com/gitlab-org/gitlab-shell/issues/209.
-
-$(dirname $0)/gitlab-shell-authorized-keys-check git git $1
# HG changeset patch
# User Valery Sizov <valery@gitlab.com>
# Date 1624394952 -10800
#      Tue Jun 22 23:49:12 2021 +0300
# Node ID 3e48e1b476145da95553f19ff8ddb8c0fcb7a3ca
# Parent  5c7ba8814ad7107e3b658abf36bea6df13ff95d7
Fix the Geo SSH push proxy hanging

Geo SSH proxy push currently impossible when the only
action that happens is branch removal. This fix
works in a way that it waits for flush packet from git
and then checks pkt lines to determine is pack data is expected.
The thing is that git doesnt send pack data when only
branch removal happens. Explanation is in
https://gitlab.com/gitlab-org/gitlab/-/issues/330494

diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -112,10 +112,32 @@
 }
 
 func (c *Command) readFromStdin() ([]byte, error) {
-	output := new(bytes.Buffer)
-	_, err := io.Copy(output, c.ReadWriter.In)
+	var output []byte
+	var needsPackData bool
+
+	scanner := pktline.NewScanner(c.ReadWriter.In)
+	for scanner.Scan() {
+		line := scanner.Bytes()
+		output = append(output, line...)
+
+		if pktline.IsFlush(line) {
+			break
+		}
 
-	return output.Bytes(), err
+		if !needsPackData && !pktline.IsRefRemoval(line) {
+			needsPackData = true
+		}
+	}
+
+	if needsPackData {
+		packData := new(bytes.Buffer)
+		_, err := io.Copy(packData, c.ReadWriter.In)
+
+		output = append(output, packData.Bytes()...)
+		return output, err
+	} else {
+		return output, nil
+	}
 }
 
 func (c *Command) readFromStdinNoEOF() []byte {
diff --git a/internal/command/shared/customaction/customaction_test.go b/internal/command/shared/customaction/customaction_test.go
--- a/internal/command/shared/customaction/customaction_test.go
+++ b/internal/command/shared/customaction/customaction_test.go
@@ -46,7 +46,7 @@
 				require.NoError(t, json.Unmarshal(b, &request))
 
 				require.Equal(t, request.Data.UserId, who)
-				require.Equal(t, "input", string(request.Output))
+				require.Equal(t, "0009input", string(request.Output))
 
 				err = json.NewEncoder(w).Encode(Response{Result: []byte("output")})
 				require.NoError(t, err)
@@ -58,7 +58,7 @@
 
 	outBuf := &bytes.Buffer{}
 	errBuf := &bytes.Buffer{}
-	input := bytes.NewBufferString("input")
+	input := bytes.NewBufferString("0009input")
 
 	response := &accessverifier.Response{
 		Who: who,
diff --git a/internal/pktline/pktline.go b/internal/pktline/pktline.go
--- a/internal/pktline/pktline.go
+++ b/internal/pktline/pktline.go
@@ -8,6 +8,7 @@
 	"bytes"
 	"fmt"
 	"io"
+	"regexp"
 	"strconv"
 )
 
@@ -16,6 +17,8 @@
 	pktDelim   = "0001"
 )
 
+var branchRemovalPktRegexp = regexp.MustCompile(`\A[a-f0-9]{4}[a-f0-9]{40} 0{40} `)
+
 // NewScanner returns a bufio.Scanner that splits on Git pktline boundaries
 func NewScanner(r io.Reader) *bufio.Scanner {
 	scanner := bufio.NewScanner(r)
@@ -24,7 +27,16 @@
 	return scanner
 }
 
-// IsDone detects the special flush packet '0009done\n'
+func IsRefRemoval(pkt []byte) bool {
+	return branchRemovalPktRegexp.Match(pkt)
+}
+
+// IsFlush detects the special flush packet '0000'
+func IsFlush(pkt []byte) bool {
+	return bytes.Equal(pkt, []byte("0000"))
+}
+
+// IsDone detects the special done packet '0009done\n'
 func IsDone(pkt []byte) bool {
 	return bytes.Equal(pkt, PktDone())
 }
diff --git a/internal/pktline/pktline_test.go b/internal/pktline/pktline_test.go
--- a/internal/pktline/pktline_test.go
+++ b/internal/pktline/pktline_test.go
@@ -68,6 +68,40 @@
 	}
 }
 
+func TestIsRefRemoval(t *testing.T) {
+	testCases := []struct {
+		in        string
+		isRemoval bool
+	}{
+		{in: "003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug", isRemoval: false},
+		{in: "003f0000000000000000000000000000000000000000 7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug", isRemoval: false},
+		{in: "003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 0000000000000000000000000000000000000000 refs/heads/debug", isRemoval: true},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.in, func(t *testing.T) {
+			require.Equal(t, tc.isRemoval, IsRefRemoval([]byte(tc.in)))
+		})
+	}
+}
+
+func TestIsFlush(t *testing.T) {
+	testCases := []struct {
+		in    string
+		flush bool
+	}{
+		{in: "0008abcd", flush: false},
+		{in: "invalid packet", flush: false},
+		{in: "0000", flush: true},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.in, func(t *testing.T) {
+			require.Equal(t, tc.flush, IsFlush([]byte(tc.in)))
+		})
+	}
+}
+
 func TestIsDone(t *testing.T) {
 	testCases := []struct {
 		in   string
diff --git a/spec/gitlab_shell_custom_git_receive_pack_spec.rb b/spec/gitlab_shell_custom_git_receive_pack_spec.rb
--- a/spec/gitlab_shell_custom_git_receive_pack_spec.rb
+++ b/spec/gitlab_shell_custom_git_receive_pack_spec.rb
@@ -74,11 +74,11 @@
           expect(stderr.gets).to eq("remote: message\n")
           expect(stderr.gets).to eq(remote_blank_line)
 
-          stdin.puts("input")
+          stdin.puts("0009input")
           stdin.close
 
           expect(stdout.gets(6)).to eq("custom")
-          expect(stdout.flush.read).to eq("input\n")
+          expect(stdout.flush.read).to eq("0009input")
         end
       end
 
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1625629452 0
#      Wed Jul 07 03:44:12 2021 +0000
# Node ID 394e15a73ea3a0a65d6ab36967159b3a17b05f34
# Parent  0a2a210a4b1d0e5f335a6c35ac3ae3d9356d4399
# Parent  3e48e1b476145da95553f19ff8ddb8c0fcb7a3ca
Merge branch '330494-geo-ssh-push-proxy-hanging-bug' into 'main'

Fix the Geo SSH push proxy hanging

See merge request gitlab-org/gitlab-shell!487

diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -112,10 +112,32 @@
 }
 
 func (c *Command) readFromStdin() ([]byte, error) {
-	output := new(bytes.Buffer)
-	_, err := io.Copy(output, c.ReadWriter.In)
+	var output []byte
+	var needsPackData bool
+
+	scanner := pktline.NewScanner(c.ReadWriter.In)
+	for scanner.Scan() {
+		line := scanner.Bytes()
+		output = append(output, line...)
+
+		if pktline.IsFlush(line) {
+			break
+		}
 
-	return output.Bytes(), err
+		if !needsPackData && !pktline.IsRefRemoval(line) {
+			needsPackData = true
+		}
+	}
+
+	if needsPackData {
+		packData := new(bytes.Buffer)
+		_, err := io.Copy(packData, c.ReadWriter.In)
+
+		output = append(output, packData.Bytes()...)
+		return output, err
+	} else {
+		return output, nil
+	}
 }
 
 func (c *Command) readFromStdinNoEOF() []byte {
diff --git a/internal/command/shared/customaction/customaction_test.go b/internal/command/shared/customaction/customaction_test.go
--- a/internal/command/shared/customaction/customaction_test.go
+++ b/internal/command/shared/customaction/customaction_test.go
@@ -46,7 +46,7 @@
 				require.NoError(t, json.Unmarshal(b, &request))
 
 				require.Equal(t, request.Data.UserId, who)
-				require.Equal(t, "input", string(request.Output))
+				require.Equal(t, "0009input", string(request.Output))
 
 				err = json.NewEncoder(w).Encode(Response{Result: []byte("output")})
 				require.NoError(t, err)
@@ -58,7 +58,7 @@
 
 	outBuf := &bytes.Buffer{}
 	errBuf := &bytes.Buffer{}
-	input := bytes.NewBufferString("input")
+	input := bytes.NewBufferString("0009input")
 
 	response := &accessverifier.Response{
 		Who: who,
diff --git a/internal/pktline/pktline.go b/internal/pktline/pktline.go
--- a/internal/pktline/pktline.go
+++ b/internal/pktline/pktline.go
@@ -8,6 +8,7 @@
 	"bytes"
 	"fmt"
 	"io"
+	"regexp"
 	"strconv"
 )
 
@@ -16,6 +17,8 @@
 	pktDelim   = "0001"
 )
 
+var branchRemovalPktRegexp = regexp.MustCompile(`\A[a-f0-9]{4}[a-f0-9]{40} 0{40} `)
+
 // NewScanner returns a bufio.Scanner that splits on Git pktline boundaries
 func NewScanner(r io.Reader) *bufio.Scanner {
 	scanner := bufio.NewScanner(r)
@@ -24,7 +27,16 @@
 	return scanner
 }
 
-// IsDone detects the special flush packet '0009done\n'
+func IsRefRemoval(pkt []byte) bool {
+	return branchRemovalPktRegexp.Match(pkt)
+}
+
+// IsFlush detects the special flush packet '0000'
+func IsFlush(pkt []byte) bool {
+	return bytes.Equal(pkt, []byte("0000"))
+}
+
+// IsDone detects the special done packet '0009done\n'
 func IsDone(pkt []byte) bool {
 	return bytes.Equal(pkt, PktDone())
 }
diff --git a/internal/pktline/pktline_test.go b/internal/pktline/pktline_test.go
--- a/internal/pktline/pktline_test.go
+++ b/internal/pktline/pktline_test.go
@@ -68,6 +68,40 @@
 	}
 }
 
+func TestIsRefRemoval(t *testing.T) {
+	testCases := []struct {
+		in        string
+		isRemoval bool
+	}{
+		{in: "003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug", isRemoval: false},
+		{in: "003f0000000000000000000000000000000000000000 7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug", isRemoval: false},
+		{in: "003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 0000000000000000000000000000000000000000 refs/heads/debug", isRemoval: true},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.in, func(t *testing.T) {
+			require.Equal(t, tc.isRemoval, IsRefRemoval([]byte(tc.in)))
+		})
+	}
+}
+
+func TestIsFlush(t *testing.T) {
+	testCases := []struct {
+		in    string
+		flush bool
+	}{
+		{in: "0008abcd", flush: false},
+		{in: "invalid packet", flush: false},
+		{in: "0000", flush: true},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.in, func(t *testing.T) {
+			require.Equal(t, tc.flush, IsFlush([]byte(tc.in)))
+		})
+	}
+}
+
 func TestIsDone(t *testing.T) {
 	testCases := []struct {
 		in   string
diff --git a/spec/gitlab_shell_custom_git_receive_pack_spec.rb b/spec/gitlab_shell_custom_git_receive_pack_spec.rb
--- a/spec/gitlab_shell_custom_git_receive_pack_spec.rb
+++ b/spec/gitlab_shell_custom_git_receive_pack_spec.rb
@@ -74,11 +74,11 @@
           expect(stderr.gets).to eq("remote: message\n")
           expect(stderr.gets).to eq(remote_blank_line)
 
-          stdin.puts("input")
+          stdin.puts("0009input")
           stdin.close
 
           expect(stdout.gets(6)).to eq("custom")
-          expect(stdout.flush.read).to eq("input\n")
+          expect(stdout.flush.read).to eq("0009input")
         end
       end
 
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1625639268 -36000
#      Wed Jul 07 16:27:48 2021 +1000
# Node ID 6bc5c90d8eedac11a02cc801fc5284a2ec66b517
# Parent  394e15a73ea3a0a65d6ab36967159b3a17b05f34
Release v13.20.0

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,11 @@
+v13.20.0
+
+- Remove bin/authorized_keys !491
+- Add a make install command !490
+- Create PROCESS.md page with Security release process !488
+- Fix the Geo SSH push proxy hanging !487
+- Standardize logging timestamp format !485
+
 v13.19.0
 
 - Don't finish the opentracing span early !466
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.19.0
+13.20.0
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1625642161 0
#      Wed Jul 07 07:16:01 2021 +0000
# Node ID 523d59dd8f0d249ff283b987905896256991e8de
# Parent  394e15a73ea3a0a65d6ab36967159b3a17b05f34
# Parent  6bc5c90d8eedac11a02cc801fc5284a2ec66b517
Merge branch 'ashmckenzie/gitlab-shell-13-20-0-release' into 'main'

Release v13.20.0

See merge request gitlab-org/gitlab-shell!492

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,11 @@
+v13.20.0
+
+- Remove bin/authorized_keys !491
+- Add a make install command !490
+- Create PROCESS.md page with Security release process !488
+- Fix the Geo SSH push proxy hanging !487
+- Standardize logging timestamp format !485
+
 v13.19.0
 
 - Don't finish the opentracing span early !466
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.19.0
+13.20.0
# HG changeset patch
# User listout <listout@protonmail.com>
# Date 1621870174 -19800
#      Mon May 24 20:59:34 2021 +0530
# Node ID 568a2c87a290540d15402667b719f539b8023945
# Parent  111f852ad4b8171ba753a814b714499e3e92ee2b
changed the format of log file to json from text

diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -57,8 +57,8 @@
 // The defaults to apply before parsing the config file(s).
 var (
 	DefaultConfig = Config{
-		LogFile:   "gitlab-shell.log",
-		LogFormat: "text",
+		LogFile:   "gitlab-shell.json",
+		LogFormat: "json",
 		Server:    DefaultServerConfig,
 		User:      "git",
 	}
# HG changeset patch
# User listout <listout@protonmail.com>
# Date 1621870336 -19800
#      Mon May 24 21:02:16 2021 +0530
# Node ID 8b2544a201455cafa554eee1143dcca987f6c4d4
# Parent  568a2c87a290540d15402667b719f539b8023945
default log format changed to json, making it reflect in example config

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -44,14 +44,14 @@
 # secret: "supersecret"
 
 # Log file.
-# Default is gitlab-shell.log in the root directory.
-# log_file: "/home/git/gitlab-shell/gitlab-shell.log"
+# Default is gitlab-shell.json in the root directory.
+# log_file: "/home/git/gitlab-shell/gitlab-shell.json"
 
 # Log level. INFO by default
 log_level: INFO
 
-# Log format. 'text' by default
-# log_format: json
+# Log format. 'json' by default
+# log_format: text
 
 # Audit usernames.
 # Set to true to see real usernames in the logs instead of key ids, which is easier to follow, but
@@ -73,7 +73,7 @@
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
-  # SSH host key files. 
+  # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
     - /run/secrets/ssh-hostkeys/ssh_host_ecdsa_key
# HG changeset patch
# User listout <listout@protonmail.com>
# Date 1622026628 -19800
#      Wed May 26 16:27:08 2021 +0530
# Node ID 91951053a07282d1920ea481ea1c7f6ff8653e2d
# Parent  8b2544a201455cafa554eee1143dcca987f6c4d4
changed filename extension to .log as json can operate on .log file

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -45,7 +45,7 @@
 
 # Log file.
 # Default is gitlab-shell.json in the root directory.
-# log_file: "/home/git/gitlab-shell/gitlab-shell.json"
+# log_file: "/home/git/gitlab-shell/gitlab-shell.log"
 
 # Log level. INFO by default
 log_level: INFO
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -57,7 +57,7 @@
 // The defaults to apply before parsing the config file(s).
 var (
 	DefaultConfig = Config{
-		LogFile:   "gitlab-shell.json",
+		LogFile:   "gitlab-shell.log",
 		LogFormat: "json",
 		Server:    DefaultServerConfig,
 		User:      "git",
# HG changeset patch
# User listout <listout@protonmail.com>
# Date 1622027080 -19800
#      Wed May 26 16:34:40 2021 +0530
# Node ID ab7d8d4a4ba471a0d97f5990a9311239779feb7f
# Parent  91951053a07282d1920ea481ea1c7f6ff8653e2d
log_format changed from 'text' to 'json'

Edited log_format description comment, if for 'text' if a user need 'text' logging

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -44,14 +44,14 @@
 # secret: "supersecret"
 
 # Log file.
-# Default is gitlab-shell.json in the root directory.
+# Default is gitlab-shell.log in the root directory.
 # log_file: "/home/git/gitlab-shell/gitlab-shell.log"
 
 # Log level. INFO by default
 log_level: INFO
 
-# Log format. 'json' by default
-# log_format: text
+# Log format. 'json' by default, can be changed to 'text' if needed
+# log_format: json
 
 # Audit usernames.
 # Set to true to see real usernames in the logs instead of key ids, which is easier to follow, but
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1626158579 0
#      Tue Jul 13 06:42:59 2021 +0000
# Node ID d17152cf742059ad42a24cbaa6bdb29659a6d5bf
# Parent  523d59dd8f0d249ff283b987905896256991e8de
# Parent  ab7d8d4a4ba471a0d97f5990a9311239779feb7f
Merge branch 'change_log_format' into 'main'

Change default logging format to JSON

See merge request gitlab-org/gitlab-shell!476

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -50,7 +50,7 @@
 # Log level. INFO by default
 log_level: INFO
 
-# Log format. 'text' by default
+# Log format. 'json' by default, can be changed to 'text' if needed
 # log_format: json
 
 # Audit usernames.
@@ -73,7 +73,7 @@
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
-  # SSH host key files. 
+  # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
     - /run/secrets/ssh-hostkeys/ssh_host_ecdsa_key
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -60,7 +60,7 @@
 var (
 	DefaultConfig = Config{
 		LogFile:   "gitlab-shell.log",
-		LogFormat: "text",
+		LogFormat: "json",
 		Server:    DefaultServerConfig,
 		User:      "git",
 	}
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1626273816 -10800
#      Wed Jul 14 17:43:36 2021 +0300
# Node ID dddfbff7e5ff07b712a1c9a21dde1e64bbdf6a09
# Parent  0a2a210a4b1d0e5f335a6c35ac3ae3d9356d4399
Refactor testhelper.PrepareTestRootDir using t.Cleanup

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -18,9 +18,7 @@
 )
 
 func TestClients(t *testing.T) {
-	testDirCleanup, err := testhelper.PrepareTestRootDir()
-	require.NoError(t, err)
-	defer testDirCleanup()
+	testhelper.PrepareTestRootDir(t)
 
 	testCases := []struct {
 		desc            string
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -98,9 +98,7 @@
 }
 
 func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string, selfSigned bool) *GitlabNetClient {
-	testDirCleanup, err := testhelper.PrepareTestRootDir()
-	require.NoError(t, err)
-	defer testDirCleanup()
+	testhelper.PrepareTestRootDir(t)
 
 	requests := []testserver.TestRequestHandler{
 		{
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -95,9 +95,7 @@
 	t.Helper()
 
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		testDirCleanup, err := testhelper.PrepareTestRootDir()
-		require.NoError(t, err)
-		defer testDirCleanup()
+		testhelper.PrepareTestRootDir(t)
 
 		t.Logf("gitlab-api-mock: received request: %s %s", r.Method, r.RequestURI)
 		w.Header().Set("Content-Type", "application/json")
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -163,9 +163,7 @@
 }
 
 func setup(t *testing.T, allowedPayload string) *Client {
-	testDirCleanup, err := testhelper.PrepareTestRootDir()
-	require.NoError(t, err)
-	defer testDirCleanup()
+	testhelper.PrepareTestRootDir(t)
 
 	body, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
 	require.NoError(t, err)
diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go
--- a/internal/testhelper/testhelper.go
+++ b/internal/testhelper/testhelper.go
@@ -7,10 +7,12 @@
 	"path"
 	"runtime"
 	"time"
+	"testing"
 
 	"github.com/otiai10/copy"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus/hooks/test"
+	"github.com/stretchr/testify/require"
 )
 
 var (
@@ -31,42 +33,21 @@
 	}
 }
 
-func PrepareTestRootDir() (func(), error) {
-	if err := os.MkdirAll(TestRoot, 0700); err != nil {
-		return nil, err
-	}
+func PrepareTestRootDir(t *testing.T) {
+	t.Helper()
 
-	var oldWd string
-	cleanup := func() {
-		if oldWd != "" {
-			err := os.Chdir(oldWd)
-			if err != nil {
-				panic(err)
-			}
-		}
+	require.NoError(t, os.MkdirAll(TestRoot, 0700))
 
-		if err := os.RemoveAll(TestRoot); err != nil {
-			panic(err)
-		}
-	}
+	t.Cleanup(func() { os.RemoveAll(TestRoot) })
 
-	if err := copyTestData(); err != nil {
-		cleanup()
-		return nil, err
-	}
+	require.NoError(t, copyTestData())
 
 	oldWd, err := os.Getwd()
-	if err != nil {
-		cleanup()
-		return nil, err
-	}
+	require.NoError(t, err)
 
-	if err := os.Chdir(TestRoot); err != nil {
-		cleanup()
-		return nil, err
-	}
+	t.Cleanup(func() { os.Chdir(oldWd) })
 
-	return cleanup, nil
+	require.NoError(t, os.Chdir(TestRoot))
 }
 
 func copyTestData() error {
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1626274506 0
#      Wed Jul 14 14:55:06 2021 +0000
# Node ID c0c264775367004fa947479834f9b2a15c7f755f
# Parent  d17152cf742059ad42a24cbaa6bdb29659a6d5bf
# Parent  dddfbff7e5ff07b712a1c9a21dde1e64bbdf6a09
Merge branch 'id-refactor-test-helper' into 'main'

Refactor testhelper.PrepareTestRootDir using t.Cleanup

See merge request gitlab-org/gitlab-shell!493

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -18,9 +18,7 @@
 )
 
 func TestClients(t *testing.T) {
-	testDirCleanup, err := testhelper.PrepareTestRootDir()
-	require.NoError(t, err)
-	defer testDirCleanup()
+	testhelper.PrepareTestRootDir(t)
 
 	testCases := []struct {
 		desc            string
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -98,9 +98,7 @@
 }
 
 func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string, selfSigned bool) *GitlabNetClient {
-	testDirCleanup, err := testhelper.PrepareTestRootDir()
-	require.NoError(t, err)
-	defer testDirCleanup()
+	testhelper.PrepareTestRootDir(t)
 
 	requests := []testserver.TestRequestHandler{
 		{
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -95,9 +95,7 @@
 	t.Helper()
 
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		testDirCleanup, err := testhelper.PrepareTestRootDir()
-		require.NoError(t, err)
-		defer testDirCleanup()
+		testhelper.PrepareTestRootDir(t)
 
 		t.Logf("gitlab-api-mock: received request: %s %s", r.Method, r.RequestURI)
 		w.Header().Set("Content-Type", "application/json")
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -163,9 +163,7 @@
 }
 
 func setup(t *testing.T, allowedPayload string) *Client {
-	testDirCleanup, err := testhelper.PrepareTestRootDir()
-	require.NoError(t, err)
-	defer testDirCleanup()
+	testhelper.PrepareTestRootDir(t)
 
 	body, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
 	require.NoError(t, err)
diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go
--- a/internal/testhelper/testhelper.go
+++ b/internal/testhelper/testhelper.go
@@ -7,10 +7,12 @@
 	"path"
 	"runtime"
 	"time"
+	"testing"
 
 	"github.com/otiai10/copy"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus/hooks/test"
+	"github.com/stretchr/testify/require"
 )
 
 var (
@@ -31,42 +33,21 @@
 	}
 }
 
-func PrepareTestRootDir() (func(), error) {
-	if err := os.MkdirAll(TestRoot, 0700); err != nil {
-		return nil, err
-	}
+func PrepareTestRootDir(t *testing.T) {
+	t.Helper()
 
-	var oldWd string
-	cleanup := func() {
-		if oldWd != "" {
-			err := os.Chdir(oldWd)
-			if err != nil {
-				panic(err)
-			}
-		}
+	require.NoError(t, os.MkdirAll(TestRoot, 0700))
 
-		if err := os.RemoveAll(TestRoot); err != nil {
-			panic(err)
-		}
-	}
+	t.Cleanup(func() { os.RemoveAll(TestRoot) })
 
-	if err := copyTestData(); err != nil {
-		cleanup()
-		return nil, err
-	}
+	require.NoError(t, copyTestData())
 
 	oldWd, err := os.Getwd()
-	if err != nil {
-		cleanup()
-		return nil, err
-	}
+	require.NoError(t, err)
 
-	if err := os.Chdir(TestRoot); err != nil {
-		cleanup()
-		return nil, err
-	}
+	t.Cleanup(func() { os.Chdir(oldWd) })
 
-	return cleanup, nil
+	require.NoError(t, os.Chdir(TestRoot))
 }
 
 func copyTestData() error {
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1625830901 -10800
#      Fri Jul 09 14:41:41 2021 +0300
# Node ID 6678b2b63861742378ad71ccc68eea953c08ca18
# Parent  c0c264775367004fa947479834f9b2a15c7f755f
Shutdown sshd gracefully

When interruption signal is sent, we are closing ssh listener to
prevent it from accepting new connections

Then after configured grace period, we cancel the context to
cancel all ongoing operations

diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -3,6 +3,10 @@
 import (
 	"flag"
 	"os"
+	"os/signal"
+	"context"
+	"syscall"
+	"time"
 
 	log "github.com/sirupsen/logrus"
 
@@ -63,6 +67,8 @@
 	ctx, finished := command.Setup("gitlab-sshd", cfg)
 	defer finished()
 
+	server := sshd.Server{Config: cfg}
+
 	// Startup monitoring endpoint.
 	if cfg.Server.WebListen != "" {
 		go func() {
@@ -75,7 +81,27 @@
 		}()
 	}
 
-	if err := sshd.Run(ctx, cfg); err != nil {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	done := make(chan os.Signal, 1)
+	signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
+
+	go func() {
+		sig := <-done
+		signal.Reset(syscall.SIGINT, syscall.SIGTERM)
+
+		log.WithFields(log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Infof("Shutdown initiated")
+
+		server.Shutdown()
+
+		<-time.After(cfg.Server.GracePeriod())
+
+		cancel()
+
+	}()
+
+	if err := server.ListenAndServe(ctx); err != nil {
 		log.Fatalf("Failed to start GitLab built-in sshd: %v", err)
 	}
 }
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -8,6 +8,7 @@
 	"path"
 	"path/filepath"
 	"sync"
+	"time"
 
 	"gitlab.com/gitlab-org/labkit/tracing"
 	yaml "gopkg.in/yaml.v2"
@@ -25,6 +26,7 @@
 	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
 	WebListen               string   `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
+	GracePeriodSeconds      uint64    `yaml:"grace_period"`
 	HostKeyFiles            []string `yaml:"host_key_files,omitempty"`
 }
 
@@ -69,6 +71,7 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
+		GracePeriodSeconds: 10,
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
@@ -77,6 +80,10 @@
 	}
 )
 
+func (sc *ServerConfig) GracePeriod() time.Duration {
+	return time.Duration(sc.GracePeriodSeconds) * time.Second
+}
+
 func (c *Config) ApplyGlobalState() {
 	if c.SslCertDir != "" {
 		os.Setenv("SSL_CERT_DIR", c.SslCertDir)
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -9,6 +9,7 @@
 	"net"
 	"strconv"
 	"time"
+	"sync"
 
 	log "github.com/sirupsen/logrus"
 
@@ -20,28 +21,87 @@
 	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
-func Run(ctx context.Context, cfg *config.Config) error {
-	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
-	if err != nil {
-		return fmt.Errorf("failed to initialize GitLab client: %w", err)
+type Server struct {
+	Config *config.Config
+
+	onShutdown bool
+	wg sync.WaitGroup
+	listener net.Listener
+}
+
+func (s *Server) ListenAndServe(ctx context.Context) error {
+	if err := s.listen(); err != nil {
+		return err
+	}
+	defer s.listener.Close()
+
+	return s.serve(ctx)
+}
+
+func (s *Server) Shutdown() error {
+	if s.listener == nil {
+		return nil
 	}
 
-	sshListener, err := net.Listen("tcp", cfg.Server.Listen)
+	s.onShutdown = true
+
+	return s.listener.Close()
+}
+
+func (s *Server) listen() error {
+	sshListener, err := net.Listen("tcp", s.Config.Server.Listen)
 	if err != nil {
 		return fmt.Errorf("failed to listen for connection: %w", err)
 	}
-	if cfg.Server.ProxyProtocol {
+
+	if s.Config.Server.ProxyProtocol {
 		sshListener = &proxyproto.Listener{Listener: sshListener}
 
 		log.Info("Proxy protocol is enabled")
 	}
-	defer sshListener.Close()
 
 	log.Infof("Listening on %v", sshListener.Addr().String())
 
+	s.listener = sshListener
+
+	return nil
+}
+
+func (s *Server) serve(ctx context.Context) error {
+	sshCfg, err := s.initConfig(ctx)
+	if err != nil {
+		return err
+	}
+
+	for {
+		nconn, err := s.listener.Accept()
+		if err != nil {
+			if s.onShutdown {
+				break
+			}
+
+			log.Warnf("Failed to accept connection: %v\n", err)
+			continue
+		}
+
+		s.wg.Add(1)
+		go s.handleConn(ctx, sshCfg, nconn)
+	}
+
+	s.wg.Wait()
+
+	return nil
+}
+
+func (s *Server) initConfig(ctx context.Context) (*ssh.ServerConfig, error) {
+	authorizedKeysClient, err := authorizedkeys.NewClient(s.Config)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+	}
+
 	sshCfg := &ssh.ServerConfig{
 		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
-			if conn.User() != cfg.User {
+			if conn.User() != s.Config.User {
 				return nil, errors.New("unknown user")
 			}
 			if key.Type() == ssh.KeyAlgoDSA {
@@ -64,7 +124,7 @@
 	}
 
 	var loadedHostKeys uint
-	for _, filename := range cfg.Server.HostKeyFiles {
+	for _, filename := range s.Config.Server.HostKeyFiles {
 		keyRaw, err := ioutil.ReadFile(filename)
 		if err != nil {
 			log.Warnf("Failed to read host key %v: %v", filename, err)
@@ -79,23 +139,17 @@
 		sshCfg.AddHostKey(key)
 	}
 	if loadedHostKeys == 0 {
-		return fmt.Errorf("No host keys could be loaded, aborting")
+		return nil, fmt.Errorf("No host keys could be loaded, aborting")
 	}
 
-	for {
-		nconn, err := sshListener.Accept()
-		if err != nil {
-			log.Warnf("Failed to accept connection: %v\n", err)
-			continue
-		}
-
-		go handleConn(ctx, cfg, sshCfg, nconn)
-	}
+	return sshCfg, nil
 }
 
-func handleConn(ctx context.Context, cfg *config.Config, sshCfg *ssh.ServerConfig, nconn net.Conn) {
+
+func (s *Server) handleConn(ctx context.Context, sshCfg *ssh.ServerConfig, nconn net.Conn) {
 	remoteAddr := nconn.RemoteAddr().String()
 
+	defer s.wg.Done()
 	defer nconn.Close()
 
 	// Prevent a panic in a single connection from taking out the whole server
@@ -116,10 +170,10 @@
 
 	go ssh.DiscardRequests(reqs)
 
-	conn := newConnection(cfg.Server.ConcurrentSessionsLimit, remoteAddr)
+	conn := newConnection(s.Config.Server.ConcurrentSessionsLimit, remoteAddr)
 	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
 		session := &session{
-			cfg:         cfg,
+			cfg:         s.Config,
 			channel:     channel,
 			gitlabKeyId: sconn.Permissions.Extensions["key-id"],
 			remoteAddr:  remoteAddr,
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/sshd_test.go
@@ -0,0 +1,49 @@
+package sshd
+
+import (
+	"testing"
+	"context"
+	"path"
+
+	"github.com/stretchr/testify/require"
+
+	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+)
+
+const serverUrl = "127.0.0.1:50000"
+
+func TestShutdown(t *testing.T) {
+	s := setupServer(t)
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	done := make(chan bool, 1)
+	go func() {
+		require.NoError(t, s.serve(ctx))
+		done <- true
+	}()
+
+	require.NoError(t, s.Shutdown())
+
+	require.True(t, <-done, "the accepting loop must be interrupted")
+}
+
+func setupServer(t *testing.T) *Server {
+	testhelper.PrepareTestRootDir(t)
+
+	url := testserver.StartSocketHttpServer(t, []testserver.TestRequestHandler{})
+	srvCfg := config.ServerConfig{
+		Listen: serverUrl,
+		HostKeyFiles: []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")},
+	}
+
+	cfg := &config.Config{RootDir: "/tmp", GitlabUrl: url, Server: srvCfg}
+
+	s := &Server{Config: cfg}
+	require.NoError(t, s.listen())
+
+	return s
+}
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1626353088 0
#      Thu Jul 15 12:44:48 2021 +0000
# Node ID a40d5d811efa26be2600db894127d84e008eee0e
# Parent  c0c264775367004fa947479834f9b2a15c7f755f
# Parent  6678b2b63861742378ad71ccc68eea953c08ca18
Merge branch 'id-cancelable-sshd' into 'main'

Shutdown sshd gracefully

See merge request gitlab-org/gitlab-shell!484

diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -3,6 +3,10 @@
 import (
 	"flag"
 	"os"
+	"os/signal"
+	"context"
+	"syscall"
+	"time"
 
 	log "github.com/sirupsen/logrus"
 
@@ -63,6 +67,8 @@
 	ctx, finished := command.Setup("gitlab-sshd", cfg)
 	defer finished()
 
+	server := sshd.Server{Config: cfg}
+
 	// Startup monitoring endpoint.
 	if cfg.Server.WebListen != "" {
 		go func() {
@@ -75,7 +81,27 @@
 		}()
 	}
 
-	if err := sshd.Run(ctx, cfg); err != nil {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	done := make(chan os.Signal, 1)
+	signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
+
+	go func() {
+		sig := <-done
+		signal.Reset(syscall.SIGINT, syscall.SIGTERM)
+
+		log.WithFields(log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Infof("Shutdown initiated")
+
+		server.Shutdown()
+
+		<-time.After(cfg.Server.GracePeriod())
+
+		cancel()
+
+	}()
+
+	if err := server.ListenAndServe(ctx); err != nil {
 		log.Fatalf("Failed to start GitLab built-in sshd: %v", err)
 	}
 }
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -8,6 +8,7 @@
 	"path"
 	"path/filepath"
 	"sync"
+	"time"
 
 	"gitlab.com/gitlab-org/labkit/tracing"
 	yaml "gopkg.in/yaml.v2"
@@ -25,6 +26,7 @@
 	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
 	WebListen               string   `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
+	GracePeriodSeconds      uint64    `yaml:"grace_period"`
 	HostKeyFiles            []string `yaml:"host_key_files,omitempty"`
 }
 
@@ -69,6 +71,7 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
+		GracePeriodSeconds: 10,
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
@@ -77,6 +80,10 @@
 	}
 )
 
+func (sc *ServerConfig) GracePeriod() time.Duration {
+	return time.Duration(sc.GracePeriodSeconds) * time.Second
+}
+
 func (c *Config) ApplyGlobalState() {
 	if c.SslCertDir != "" {
 		os.Setenv("SSL_CERT_DIR", c.SslCertDir)
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -9,6 +9,7 @@
 	"net"
 	"strconv"
 	"time"
+	"sync"
 
 	log "github.com/sirupsen/logrus"
 
@@ -20,28 +21,87 @@
 	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
-func Run(ctx context.Context, cfg *config.Config) error {
-	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
-	if err != nil {
-		return fmt.Errorf("failed to initialize GitLab client: %w", err)
+type Server struct {
+	Config *config.Config
+
+	onShutdown bool
+	wg sync.WaitGroup
+	listener net.Listener
+}
+
+func (s *Server) ListenAndServe(ctx context.Context) error {
+	if err := s.listen(); err != nil {
+		return err
+	}
+	defer s.listener.Close()
+
+	return s.serve(ctx)
+}
+
+func (s *Server) Shutdown() error {
+	if s.listener == nil {
+		return nil
 	}
 
-	sshListener, err := net.Listen("tcp", cfg.Server.Listen)
+	s.onShutdown = true
+
+	return s.listener.Close()
+}
+
+func (s *Server) listen() error {
+	sshListener, err := net.Listen("tcp", s.Config.Server.Listen)
 	if err != nil {
 		return fmt.Errorf("failed to listen for connection: %w", err)
 	}
-	if cfg.Server.ProxyProtocol {
+
+	if s.Config.Server.ProxyProtocol {
 		sshListener = &proxyproto.Listener{Listener: sshListener}
 
 		log.Info("Proxy protocol is enabled")
 	}
-	defer sshListener.Close()
 
 	log.Infof("Listening on %v", sshListener.Addr().String())
 
+	s.listener = sshListener
+
+	return nil
+}
+
+func (s *Server) serve(ctx context.Context) error {
+	sshCfg, err := s.initConfig(ctx)
+	if err != nil {
+		return err
+	}
+
+	for {
+		nconn, err := s.listener.Accept()
+		if err != nil {
+			if s.onShutdown {
+				break
+			}
+
+			log.Warnf("Failed to accept connection: %v\n", err)
+			continue
+		}
+
+		s.wg.Add(1)
+		go s.handleConn(ctx, sshCfg, nconn)
+	}
+
+	s.wg.Wait()
+
+	return nil
+}
+
+func (s *Server) initConfig(ctx context.Context) (*ssh.ServerConfig, error) {
+	authorizedKeysClient, err := authorizedkeys.NewClient(s.Config)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+	}
+
 	sshCfg := &ssh.ServerConfig{
 		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
-			if conn.User() != cfg.User {
+			if conn.User() != s.Config.User {
 				return nil, errors.New("unknown user")
 			}
 			if key.Type() == ssh.KeyAlgoDSA {
@@ -64,7 +124,7 @@
 	}
 
 	var loadedHostKeys uint
-	for _, filename := range cfg.Server.HostKeyFiles {
+	for _, filename := range s.Config.Server.HostKeyFiles {
 		keyRaw, err := ioutil.ReadFile(filename)
 		if err != nil {
 			log.Warnf("Failed to read host key %v: %v", filename, err)
@@ -79,23 +139,17 @@
 		sshCfg.AddHostKey(key)
 	}
 	if loadedHostKeys == 0 {
-		return fmt.Errorf("No host keys could be loaded, aborting")
+		return nil, fmt.Errorf("No host keys could be loaded, aborting")
 	}
 
-	for {
-		nconn, err := sshListener.Accept()
-		if err != nil {
-			log.Warnf("Failed to accept connection: %v\n", err)
-			continue
-		}
-
-		go handleConn(ctx, cfg, sshCfg, nconn)
-	}
+	return sshCfg, nil
 }
 
-func handleConn(ctx context.Context, cfg *config.Config, sshCfg *ssh.ServerConfig, nconn net.Conn) {
+
+func (s *Server) handleConn(ctx context.Context, sshCfg *ssh.ServerConfig, nconn net.Conn) {
 	remoteAddr := nconn.RemoteAddr().String()
 
+	defer s.wg.Done()
 	defer nconn.Close()
 
 	// Prevent a panic in a single connection from taking out the whole server
@@ -116,10 +170,10 @@
 
 	go ssh.DiscardRequests(reqs)
 
-	conn := newConnection(cfg.Server.ConcurrentSessionsLimit, remoteAddr)
+	conn := newConnection(s.Config.Server.ConcurrentSessionsLimit, remoteAddr)
 	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
 		session := &session{
-			cfg:         cfg,
+			cfg:         s.Config,
 			channel:     channel,
 			gitlabKeyId: sconn.Permissions.Extensions["key-id"],
 			remoteAddr:  remoteAddr,
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/sshd_test.go
@@ -0,0 +1,49 @@
+package sshd
+
+import (
+	"testing"
+	"context"
+	"path"
+
+	"github.com/stretchr/testify/require"
+
+	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+)
+
+const serverUrl = "127.0.0.1:50000"
+
+func TestShutdown(t *testing.T) {
+	s := setupServer(t)
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	done := make(chan bool, 1)
+	go func() {
+		require.NoError(t, s.serve(ctx))
+		done <- true
+	}()
+
+	require.NoError(t, s.Shutdown())
+
+	require.True(t, <-done, "the accepting loop must be interrupted")
+}
+
+func setupServer(t *testing.T) *Server {
+	testhelper.PrepareTestRootDir(t)
+
+	url := testserver.StartSocketHttpServer(t, []testserver.TestRequestHandler{})
+	srvCfg := config.ServerConfig{
+		Listen: serverUrl,
+		HostKeyFiles: []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")},
+	}
+
+	cfg := &config.Config{RootDir: "/tmp", GitlabUrl: url, Server: srvCfg}
+
+	s := &Server{Config: cfg}
+	require.NoError(t, s.listen())
+
+	return s
+}
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1626349267 -10800
#      Thu Jul 15 14:41:07 2021 +0300
# Node ID c9a651a976232ad89780083dcaf03b1e249692bf
# Parent  a40d5d811efa26be2600db894127d84e008eee0e
Provide liveness and readiness probes

They are going to be used to determine whether a server is alive
and ready to accept traffic

diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -76,6 +76,7 @@
 				monitoring.Start(
 					monitoring.WithListenerAddress(cfg.Server.WebListen),
 					monitoring.WithBuildInformation(Version, BuildTime),
+					monitoring.WithServeMux(server.MonitoringServeMux()),
 				),
 			)
 		}()
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -26,7 +26,9 @@
 	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
 	WebListen               string   `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
-	GracePeriodSeconds      uint64    `yaml:"grace_period"`
+	GracePeriodSeconds      uint64   `yaml:"grace_period"`
+	ReadinessProbe          string   `yaml:"readiness_probe"`
+	LivenessProbe           string   `yaml:"liveness_probe"`
 	HostKeyFiles            []string `yaml:"host_key_files,omitempty"`
 }
 
@@ -72,6 +74,8 @@
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
 		GracePeriodSeconds: 10,
+		ReadinessProbe: "/start",
+		LivenessProbe: "/health",
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -10,6 +10,7 @@
 	"strconv"
 	"time"
 	"sync"
+	"net/http"
 
 	log "github.com/sirupsen/logrus"
 
@@ -21,10 +22,20 @@
 	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
+type status int
+
+const(
+	StatusStarting status = iota
+	StatusReady
+	StatusOnShutdown
+	StatusClosed
+)
+
 type Server struct {
 	Config *config.Config
 
-	onShutdown bool
+	status status
+	statusMu sync.Mutex
 	wg sync.WaitGroup
 	listener net.Listener
 }
@@ -43,11 +54,29 @@
 		return nil
 	}
 
-	s.onShutdown = true
+	s.changeStatus(StatusOnShutdown)
 
 	return s.listener.Close()
 }
 
+func (s *Server) MonitoringServeMux() *http.ServeMux {
+	mux := http.NewServeMux()
+
+	mux.HandleFunc(s.Config.Server.ReadinessProbe, func(w http.ResponseWriter, r *http.Request) {
+		if s.getStatus() == StatusReady {
+			w.WriteHeader(http.StatusOK)
+		} else {
+			w.WriteHeader(http.StatusServiceUnavailable)
+		}
+	})
+
+	mux.HandleFunc(s.Config.Server.LivenessProbe, func(w http.ResponseWriter, r *http.Request) {
+			w.WriteHeader(http.StatusOK)
+	})
+
+	return mux
+}
+
 func (s *Server) listen() error {
 	sshListener, err := net.Listen("tcp", s.Config.Server.Listen)
 	if err != nil {
@@ -73,10 +102,12 @@
 		return err
 	}
 
+	s.changeStatus(StatusReady)
+
 	for {
 		nconn, err := s.listener.Accept()
 		if err != nil {
-			if s.onShutdown {
+			if s.getStatus() == StatusOnShutdown {
 				break
 			}
 
@@ -90,9 +121,24 @@
 
 	s.wg.Wait()
 
+	s.changeStatus(StatusClosed)
+
 	return nil
 }
 
+func (s *Server) changeStatus(st status) {
+	s.statusMu.Lock()
+	s.status = st
+	s.statusMu.Unlock()
+}
+
+func (s *Server) getStatus() status {
+	s.statusMu.Lock()
+	defer s.statusMu.Unlock()
+
+	return s.status
+}
+
 func (s *Server) initConfig(ctx context.Context) (*ssh.ServerConfig, error) {
 	authorizedKeysClient, err := authorizedkeys.NewClient(s.Config)
 	if err != nil {
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -4,6 +4,8 @@
 	"testing"
 	"context"
 	"path"
+	"net/http/httptest"
+	"time"
 
 	"github.com/stretchr/testify/require"
 
@@ -17,18 +19,55 @@
 func TestShutdown(t *testing.T) {
 	s := setupServer(t)
 
-	ctx, cancel := context.WithCancel(context.Background())
-	defer cancel()
+	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
 
-	done := make(chan bool, 1)
-	go func() {
-		require.NoError(t, s.serve(ctx))
-		done <- true
-	}()
+	verifyStatus(t, s, StatusReady)
+
+	s.wg.Add(1)
 
 	require.NoError(t, s.Shutdown())
+	verifyStatus(t, s, StatusOnShutdown)
 
-	require.True(t, <-done, "the accepting loop must be interrupted")
+	s.wg.Done()
+
+	verifyStatus(t, s, StatusClosed)
+}
+
+func TestReadinessProbe(t *testing.T) {
+	s := &Server{Config: &config.Config{Server: config.DefaultServerConfig}}
+
+	require.Equal(t, StatusStarting, s.getStatus())
+
+	mux := s.MonitoringServeMux()
+
+	req := httptest.NewRequest("GET", "/start", nil)
+
+	r := httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 503, r.Result().StatusCode)
+
+	s.changeStatus(StatusReady)
+
+	r = httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 200, r.Result().StatusCode)
+
+	s.changeStatus(StatusOnShutdown)
+
+	r = httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 503, r.Result().StatusCode)
+}
+
+func TestLivenessProbe(t *testing.T) {
+	s := &Server{Config: &config.Config{Server: config.DefaultServerConfig}}
+	mux := s.MonitoringServeMux()
+
+	req := httptest.NewRequest("GET", "/health", nil)
+
+	r := httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 200, r.Result().StatusCode)
 }
 
 func setupServer(t *testing.T) *Server {
@@ -42,8 +81,18 @@
 
 	cfg := &config.Config{RootDir: "/tmp", GitlabUrl: url, Server: srvCfg}
 
-	s := &Server{Config: cfg}
-	require.NoError(t, s.listen())
+	return &Server{Config: cfg}
+}
 
-	return s
+func verifyStatus(t *testing.T, s *Server, st status) {
+	for i := 5; i < 500; i+=50 {
+		if s.getStatus() == st {
+			break
+		}
+
+		// Sleep incrementally ~2s in total
+		time.Sleep(time.Duration(i) * time.Millisecond)
+	}
+
+	require.Equal(t, s.getStatus(), st)
 }
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1626701357 0
#      Mon Jul 19 13:29:17 2021 +0000
# Node ID 257a87a032b82d016afbcd37d34e19cd1524bab1
# Parent  a40d5d811efa26be2600db894127d84e008eee0e
# Parent  c9a651a976232ad89780083dcaf03b1e249692bf
Merge branch 'id-kubernetes-probes' into 'main'

Provide liveness and readiness probes

See merge request gitlab-org/gitlab-shell!494

diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -76,6 +76,7 @@
 				monitoring.Start(
 					monitoring.WithListenerAddress(cfg.Server.WebListen),
 					monitoring.WithBuildInformation(Version, BuildTime),
+					monitoring.WithServeMux(server.MonitoringServeMux()),
 				),
 			)
 		}()
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -26,7 +26,9 @@
 	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
 	WebListen               string   `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
-	GracePeriodSeconds      uint64    `yaml:"grace_period"`
+	GracePeriodSeconds      uint64   `yaml:"grace_period"`
+	ReadinessProbe          string   `yaml:"readiness_probe"`
+	LivenessProbe           string   `yaml:"liveness_probe"`
 	HostKeyFiles            []string `yaml:"host_key_files,omitempty"`
 }
 
@@ -72,6 +74,8 @@
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
 		GracePeriodSeconds: 10,
+		ReadinessProbe: "/start",
+		LivenessProbe: "/health",
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -10,6 +10,7 @@
 	"strconv"
 	"time"
 	"sync"
+	"net/http"
 
 	log "github.com/sirupsen/logrus"
 
@@ -21,10 +22,20 @@
 	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
+type status int
+
+const(
+	StatusStarting status = iota
+	StatusReady
+	StatusOnShutdown
+	StatusClosed
+)
+
 type Server struct {
 	Config *config.Config
 
-	onShutdown bool
+	status status
+	statusMu sync.Mutex
 	wg sync.WaitGroup
 	listener net.Listener
 }
@@ -43,11 +54,29 @@
 		return nil
 	}
 
-	s.onShutdown = true
+	s.changeStatus(StatusOnShutdown)
 
 	return s.listener.Close()
 }
 
+func (s *Server) MonitoringServeMux() *http.ServeMux {
+	mux := http.NewServeMux()
+
+	mux.HandleFunc(s.Config.Server.ReadinessProbe, func(w http.ResponseWriter, r *http.Request) {
+		if s.getStatus() == StatusReady {
+			w.WriteHeader(http.StatusOK)
+		} else {
+			w.WriteHeader(http.StatusServiceUnavailable)
+		}
+	})
+
+	mux.HandleFunc(s.Config.Server.LivenessProbe, func(w http.ResponseWriter, r *http.Request) {
+			w.WriteHeader(http.StatusOK)
+	})
+
+	return mux
+}
+
 func (s *Server) listen() error {
 	sshListener, err := net.Listen("tcp", s.Config.Server.Listen)
 	if err != nil {
@@ -73,10 +102,12 @@
 		return err
 	}
 
+	s.changeStatus(StatusReady)
+
 	for {
 		nconn, err := s.listener.Accept()
 		if err != nil {
-			if s.onShutdown {
+			if s.getStatus() == StatusOnShutdown {
 				break
 			}
 
@@ -90,9 +121,24 @@
 
 	s.wg.Wait()
 
+	s.changeStatus(StatusClosed)
+
 	return nil
 }
 
+func (s *Server) changeStatus(st status) {
+	s.statusMu.Lock()
+	s.status = st
+	s.statusMu.Unlock()
+}
+
+func (s *Server) getStatus() status {
+	s.statusMu.Lock()
+	defer s.statusMu.Unlock()
+
+	return s.status
+}
+
 func (s *Server) initConfig(ctx context.Context) (*ssh.ServerConfig, error) {
 	authorizedKeysClient, err := authorizedkeys.NewClient(s.Config)
 	if err != nil {
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -4,6 +4,8 @@
 	"testing"
 	"context"
 	"path"
+	"net/http/httptest"
+	"time"
 
 	"github.com/stretchr/testify/require"
 
@@ -17,18 +19,55 @@
 func TestShutdown(t *testing.T) {
 	s := setupServer(t)
 
-	ctx, cancel := context.WithCancel(context.Background())
-	defer cancel()
+	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
 
-	done := make(chan bool, 1)
-	go func() {
-		require.NoError(t, s.serve(ctx))
-		done <- true
-	}()
+	verifyStatus(t, s, StatusReady)
+
+	s.wg.Add(1)
 
 	require.NoError(t, s.Shutdown())
+	verifyStatus(t, s, StatusOnShutdown)
 
-	require.True(t, <-done, "the accepting loop must be interrupted")
+	s.wg.Done()
+
+	verifyStatus(t, s, StatusClosed)
+}
+
+func TestReadinessProbe(t *testing.T) {
+	s := &Server{Config: &config.Config{Server: config.DefaultServerConfig}}
+
+	require.Equal(t, StatusStarting, s.getStatus())
+
+	mux := s.MonitoringServeMux()
+
+	req := httptest.NewRequest("GET", "/start", nil)
+
+	r := httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 503, r.Result().StatusCode)
+
+	s.changeStatus(StatusReady)
+
+	r = httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 200, r.Result().StatusCode)
+
+	s.changeStatus(StatusOnShutdown)
+
+	r = httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 503, r.Result().StatusCode)
+}
+
+func TestLivenessProbe(t *testing.T) {
+	s := &Server{Config: &config.Config{Server: config.DefaultServerConfig}}
+	mux := s.MonitoringServeMux()
+
+	req := httptest.NewRequest("GET", "/health", nil)
+
+	r := httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 200, r.Result().StatusCode)
 }
 
 func setupServer(t *testing.T) *Server {
@@ -42,8 +81,18 @@
 
 	cfg := &config.Config{RootDir: "/tmp", GitlabUrl: url, Server: srvCfg}
 
-	s := &Server{Config: cfg}
-	require.NoError(t, s.listen())
+	return &Server{Config: cfg}
+}
 
-	return s
+func verifyStatus(t *testing.T, s *Server, st status) {
+	for i := 5; i < 500; i+=50 {
+		if s.getStatus() == st {
+			break
+		}
+
+		// Sleep incrementally ~2s in total
+		time.Sleep(time.Duration(i) * time.Millisecond)
+	}
+
+	require.Equal(t, s.getStatus(), st)
 }
# HG changeset patch
# User Igor <iwiedler@gitlab.com>
# Date 1626793946 0
#      Tue Jul 20 15:12:26 2021 +0000
# Node ID c30b94f8fc7a2289775e8ea552be684138ba4ec8
# Parent  257a87a032b82d016afbcd37d34e19cd1524bab1
Add tracing instrumentation to http client

diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -14,6 +14,7 @@
 
 	log "github.com/sirupsen/logrus"
 	"gitlab.com/gitlab-org/labkit/correlation"
+	"gitlab.com/gitlab-org/labkit/tracing"
 )
 
 const (
@@ -85,7 +86,7 @@
 	}
 
 	c := &http.Client{
-		Transport: correlation.NewInstrumentedRoundTripper(transport),
+		Transport: correlation.NewInstrumentedRoundTripper(tracing.NewRoundTripper(transport)),
 		Timeout:   readTimeout(readTimeoutSeconds),
 	}
 
# HG changeset patch
# User Igor Wiedler <iwiedler@gitlab.com>
# Date 1626798262 -7200
#      Tue Jul 20 18:24:22 2021 +0200
# Node ID 496c683df4ed7cca478a635a5c65628201a996eb
# Parent  c30b94f8fc7a2289775e8ea552be684138ba4ec8
remove tracing.NewRoundTripper from internal/Config, now that NewHTTPClient already includes it

diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -10,7 +10,6 @@
 	"sync"
 	"time"
 
-	"gitlab.com/gitlab-org/labkit/tracing"
 	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
@@ -73,9 +72,9 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
-		GracePeriodSeconds: 10,
-		ReadinessProbe: "/start",
-		LivenessProbe: "/health",
+		GracePeriodSeconds:      10,
+		ReadinessProbe:          "/start",
+		LivenessProbe:           "/health",
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
@@ -96,7 +95,7 @@
 
 func (c *Config) HttpClient() *client.HttpClient {
 	c.httpClientOnce.Do(func() {
-		client := client.NewHTTPClient(
+		c.httpClient = client.NewHTTPClient(
 			c.GitlabUrl,
 			c.GitlabRelativeURLRoot,
 			c.HttpSettings.CaFile,
@@ -104,11 +103,6 @@
 			c.HttpSettings.SelfSignedCert,
 			c.HttpSettings.ReadTimeoutSeconds,
 		)
-
-		tr := client.Transport
-		client.Transport = tracing.NewRoundTripper(tr)
-
-		c.httpClient = client
 	})
 
 	return c.httpClient
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1626798669 0
#      Tue Jul 20 16:31:09 2021 +0000
# Node ID 66984f720ad0593eed17c66ac852cff1a03dae76
# Parent  257a87a032b82d016afbcd37d34e19cd1524bab1
# Parent  496c683df4ed7cca478a635a5c65628201a996eb
Merge branch 'igorwwwwwwwwwwwwwwwwwwww-main-patch-84756' into 'main'

Add tracing instrumentation to http client

See merge request gitlab-org/gitlab-shell!495

diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -14,6 +14,7 @@
 
 	log "github.com/sirupsen/logrus"
 	"gitlab.com/gitlab-org/labkit/correlation"
+	"gitlab.com/gitlab-org/labkit/tracing"
 )
 
 const (
@@ -85,7 +86,7 @@
 	}
 
 	c := &http.Client{
-		Transport: correlation.NewInstrumentedRoundTripper(transport),
+		Transport: correlation.NewInstrumentedRoundTripper(tracing.NewRoundTripper(transport)),
 		Timeout:   readTimeout(readTimeoutSeconds),
 	}
 
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -10,7 +10,6 @@
 	"sync"
 	"time"
 
-	"gitlab.com/gitlab-org/labkit/tracing"
 	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
@@ -73,9 +72,9 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
-		GracePeriodSeconds: 10,
-		ReadinessProbe: "/start",
-		LivenessProbe: "/health",
+		GracePeriodSeconds:      10,
+		ReadinessProbe:          "/start",
+		LivenessProbe:           "/health",
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
@@ -96,7 +95,7 @@
 
 func (c *Config) HttpClient() *client.HttpClient {
 	c.httpClientOnce.Do(func() {
-		client := client.NewHTTPClient(
+		c.httpClient = client.NewHTTPClient(
 			c.GitlabUrl,
 			c.GitlabRelativeURLRoot,
 			c.HttpSettings.CaFile,
@@ -104,11 +103,6 @@
 			c.HttpSettings.SelfSignedCert,
 			c.HttpSettings.ReadTimeoutSeconds,
 		)
-
-		tr := client.Transport
-		client.Transport = tracing.NewRoundTripper(tr)
-
-		c.httpClient = client
 	})
 
 	return c.httpClient
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1626855840 -10800
#      Wed Jul 21 11:24:00 2021 +0300
# Node ID 485cf435129a7f60571720785cf4944ac4aa3d6c
# Parent  66984f720ad0593eed17c66ac852cff1a03dae76
Prometheus metrics for HTTP requests

A RoundTripper for tracking the duration of an http request
is introduced

diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -10,9 +10,11 @@
 	"sync"
 	"time"
 
+	"github.com/prometheus/client_golang/prometheus/promhttp"
 	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
 
 const (
@@ -95,7 +97,7 @@
 
 func (c *Config) HttpClient() *client.HttpClient {
 	c.httpClientOnce.Do(func() {
-		c.httpClient = client.NewHTTPClient(
+		client := client.NewHTTPClient(
 			c.GitlabUrl,
 			c.GitlabRelativeURLRoot,
 			c.HttpSettings.CaFile,
@@ -103,6 +105,12 @@
 			c.HttpSettings.SelfSignedCert,
 			c.HttpSettings.ReadTimeoutSeconds,
 		)
+
+		tr := client.Transport
+		client.Transport = promhttp.InstrumentRoundTripperDuration(metrics.HttpRequestDuration, tr)
+
+
+		c.httpClient = client
 	})
 
 	return c.httpClient
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -5,8 +5,10 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
+	"github.com/prometheus/client_golang/prometheus"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 )
 
 func TestConfigApplyGlobalState(t *testing.T) {
@@ -22,3 +24,27 @@
 
 	require.Equal(t, "foo", os.Getenv("SSL_CERT_DIR"))
 }
+
+func TestHttpClient(t *testing.T) {
+	url := testserver.StartHttpServer(t, []testserver.TestRequestHandler{})
+
+	config := &Config{GitlabUrl: url}
+	client := config.HttpClient()
+
+	_, err := client.Get("http://host.com/path")
+	require.NoError(t, err)
+
+	ms, err := prometheus.DefaultGatherer.Gather()
+	require.NoError(t, err)
+
+	lastMetric := ms[0]
+	require.Equal(t, lastMetric.GetName(), "gitlab_shell_http_request_seconds")
+
+	labels := lastMetric.GetMetric()[0].Label
+
+	require.Equal(t, "code", labels[0].GetName())
+	require.Equal(t, "404", labels[0].GetValue())
+
+	require.Equal(t, "method", labels[1].GetName())
+	require.Equal(t, "get", labels[1].GetValue())
+}
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
new file mode 100644
--- /dev/null
+++ b/internal/metrics/metrics.go
@@ -0,0 +1,63 @@
+package metrics
+
+import (
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promauto"
+)
+
+const (
+	namespace     = "gitlab_shell"
+	sshdSubsystem = "sshd"
+	httpSubsystem = "http"
+)
+
+var (
+	SshdConnectionDuration = promauto.NewHistogram(
+		prometheus.HistogramOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      "connection_duration_seconds",
+			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
+			Buckets: []float64{
+				0.005, /* 5ms */
+				0.025, /* 25ms */
+				0.1,   /* 100ms */
+				0.5,   /* 500ms */
+				1.0,   /* 1s */
+				10.0,  /* 10s */
+				30.0,  /* 30s */
+				60.0,  /* 1m */
+				300.0, /* 5m */
+			},
+		},
+	)
+
+	SshdHitMaxSessions = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      "concurrent_limited_sessions_total",
+			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
+		},
+	)
+
+	HttpRequestDuration = promauto.NewHistogramVec(
+		prometheus.HistogramOpts{
+			Namespace: namespace,
+			Subsystem: httpSubsystem,
+			Name:      "request_seconds",
+			Help:      "A histogram of latencies for gitlab-shell http requests",
+			Buckets: []float64{
+				0.01, /* 10ms */
+				0.05, /* 50ms */
+				0.1,  /* 100ms */
+				0.25, /* 250ms */
+				0.5,  /* 500ms */
+				1.0,  /* 1s */
+				5.0,  /* 5s */
+				10.0, /* 10s */
+			},
+		},
+		[]string{"code", "method"},
+	)
+)
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -4,47 +4,11 @@
 	"context"
 	"time"
 
-	"github.com/prometheus/client_golang/prometheus"
-	"github.com/prometheus/client_golang/prometheus/promauto"
 	log "github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
-)
 
-const (
-	namespace     = "gitlab_shell"
-	sshdSubsystem = "sshd"
-)
-
-var (
-	sshdConnectionDuration = promauto.NewHistogram(
-		prometheus.HistogramOpts{
-			Namespace: namespace,
-			Subsystem: sshdSubsystem,
-			Name:      "connection_duration_seconds",
-			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
-			Buckets: []float64{
-				0.005, /* 5ms */
-				0.025, /* 25ms */
-				0.1,   /* 100ms */
-				0.5,   /* 500ms */
-				1.0,   /* 1s */
-				10.0,  /* 10s */
-				30.0,  /* 30s */
-				60.0,  /* 1m */
-				300.0, /* 5m */
-			},
-		},
-	)
-
-	sshdHitMaxSessions = promauto.NewCounter(
-		prometheus.CounterOpts{
-			Namespace: namespace,
-			Subsystem: sshdSubsystem,
-			Name:      "concurrent_limited_sessions_total",
-			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
-		},
-	)
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
 
 type connection struct {
@@ -64,7 +28,7 @@
 }
 
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
-	defer sshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
+	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
 
 	for newChannel := range chans {
 		if newChannel.ChannelType() != "session" {
@@ -73,7 +37,7 @@
 		}
 		if !c.concurrentSessions.TryAcquire(1) {
 			newChannel.Reject(ssh.ResourceShortage, "too many concurrent sessions")
-			sshdHitMaxSessions.Inc()
+			metrics.SshdHitMaxSessions.Inc()
 			continue
 		}
 		channel, requests, err := newChannel.Accept()
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1626871593 0
#      Wed Jul 21 12:46:33 2021 +0000
# Node ID 23032cdc93aadbbdd6ded07c42637c58cfac13d2
# Parent  66984f720ad0593eed17c66ac852cff1a03dae76
# Parent  485cf435129a7f60571720785cf4944ac4aa3d6c
Merge branch 'id-prometheus-metrics-for-http' into 'main'

Prometheus metrics for HTTP requests

See merge request gitlab-org/gitlab-shell!496

diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -10,9 +10,11 @@
 	"sync"
 	"time"
 
+	"github.com/prometheus/client_golang/prometheus/promhttp"
 	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
 
 const (
@@ -95,7 +97,7 @@
 
 func (c *Config) HttpClient() *client.HttpClient {
 	c.httpClientOnce.Do(func() {
-		c.httpClient = client.NewHTTPClient(
+		client := client.NewHTTPClient(
 			c.GitlabUrl,
 			c.GitlabRelativeURLRoot,
 			c.HttpSettings.CaFile,
@@ -103,6 +105,12 @@
 			c.HttpSettings.SelfSignedCert,
 			c.HttpSettings.ReadTimeoutSeconds,
 		)
+
+		tr := client.Transport
+		client.Transport = promhttp.InstrumentRoundTripperDuration(metrics.HttpRequestDuration, tr)
+
+
+		c.httpClient = client
 	})
 
 	return c.httpClient
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -5,8 +5,10 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
+	"github.com/prometheus/client_golang/prometheus"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 )
 
 func TestConfigApplyGlobalState(t *testing.T) {
@@ -22,3 +24,27 @@
 
 	require.Equal(t, "foo", os.Getenv("SSL_CERT_DIR"))
 }
+
+func TestHttpClient(t *testing.T) {
+	url := testserver.StartHttpServer(t, []testserver.TestRequestHandler{})
+
+	config := &Config{GitlabUrl: url}
+	client := config.HttpClient()
+
+	_, err := client.Get("http://host.com/path")
+	require.NoError(t, err)
+
+	ms, err := prometheus.DefaultGatherer.Gather()
+	require.NoError(t, err)
+
+	lastMetric := ms[0]
+	require.Equal(t, lastMetric.GetName(), "gitlab_shell_http_request_seconds")
+
+	labels := lastMetric.GetMetric()[0].Label
+
+	require.Equal(t, "code", labels[0].GetName())
+	require.Equal(t, "404", labels[0].GetValue())
+
+	require.Equal(t, "method", labels[1].GetName())
+	require.Equal(t, "get", labels[1].GetValue())
+}
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
new file mode 100644
--- /dev/null
+++ b/internal/metrics/metrics.go
@@ -0,0 +1,63 @@
+package metrics
+
+import (
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promauto"
+)
+
+const (
+	namespace     = "gitlab_shell"
+	sshdSubsystem = "sshd"
+	httpSubsystem = "http"
+)
+
+var (
+	SshdConnectionDuration = promauto.NewHistogram(
+		prometheus.HistogramOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      "connection_duration_seconds",
+			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
+			Buckets: []float64{
+				0.005, /* 5ms */
+				0.025, /* 25ms */
+				0.1,   /* 100ms */
+				0.5,   /* 500ms */
+				1.0,   /* 1s */
+				10.0,  /* 10s */
+				30.0,  /* 30s */
+				60.0,  /* 1m */
+				300.0, /* 5m */
+			},
+		},
+	)
+
+	SshdHitMaxSessions = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      "concurrent_limited_sessions_total",
+			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
+		},
+	)
+
+	HttpRequestDuration = promauto.NewHistogramVec(
+		prometheus.HistogramOpts{
+			Namespace: namespace,
+			Subsystem: httpSubsystem,
+			Name:      "request_seconds",
+			Help:      "A histogram of latencies for gitlab-shell http requests",
+			Buckets: []float64{
+				0.01, /* 10ms */
+				0.05, /* 50ms */
+				0.1,  /* 100ms */
+				0.25, /* 250ms */
+				0.5,  /* 500ms */
+				1.0,  /* 1s */
+				5.0,  /* 5s */
+				10.0, /* 10s */
+			},
+		},
+		[]string{"code", "method"},
+	)
+)
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -4,47 +4,11 @@
 	"context"
 	"time"
 
-	"github.com/prometheus/client_golang/prometheus"
-	"github.com/prometheus/client_golang/prometheus/promauto"
 	log "github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
-)
 
-const (
-	namespace     = "gitlab_shell"
-	sshdSubsystem = "sshd"
-)
-
-var (
-	sshdConnectionDuration = promauto.NewHistogram(
-		prometheus.HistogramOpts{
-			Namespace: namespace,
-			Subsystem: sshdSubsystem,
-			Name:      "connection_duration_seconds",
-			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
-			Buckets: []float64{
-				0.005, /* 5ms */
-				0.025, /* 25ms */
-				0.1,   /* 100ms */
-				0.5,   /* 500ms */
-				1.0,   /* 1s */
-				10.0,  /* 10s */
-				30.0,  /* 30s */
-				60.0,  /* 1m */
-				300.0, /* 5m */
-			},
-		},
-	)
-
-	sshdHitMaxSessions = promauto.NewCounter(
-		prometheus.CounterOpts{
-			Namespace: namespace,
-			Subsystem: sshdSubsystem,
-			Name:      "concurrent_limited_sessions_total",
-			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
-		},
-	)
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
 
 type connection struct {
@@ -64,7 +28,7 @@
 }
 
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
-	defer sshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
+	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
 
 	for newChannel := range chans {
 		if newChannel.ChannelType() != "session" {
@@ -73,7 +37,7 @@
 		}
 		if !c.concurrentSessions.TryAcquire(1) {
 			newChannel.Reject(ssh.ResourceShortage, "too many concurrent sessions")
-			sshdHitMaxSessions.Inc()
+			metrics.SshdHitMaxSessions.Inc()
 			continue
 		}
 		channel, requests, err := newChannel.Accept()
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1626874353 -3600
#      Wed Jul 21 14:32:33 2021 +0100
# Node ID 293e15251974026e8214c688c3d5c114691cdf86
# Parent  23032cdc93aadbbdd6ded07c42637c58cfac13d2
Unit tests for internal/sshd/connection.go

diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -2,6 +2,7 @@
 
 import (
 	"context"
+	"errors"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -9,22 +10,31 @@
 )
 
 type rejectCall struct {
-	reason ssh.RejectionReason
+	reason  ssh.RejectionReason
 	message string
 }
 
 type fakeNewChannel struct {
 	channelType string
 	extraData   []byte
+	acceptErr   error
+
+	acceptCh chan struct{}
 	rejectCh chan rejectCall
 }
 
 func (f *fakeNewChannel) Accept() (ssh.Channel, <-chan *ssh.Request, error) {
-	return nil, nil, nil
+	if f.acceptCh != nil {
+		f.acceptCh <- struct{}{}
+	}
+
+	return nil, nil, f.acceptErr
 }
 
 func (f *fakeNewChannel) Reject(reason ssh.RejectionReason, message string) error {
-	f.rejectCh <- rejectCall{reason: reason, message: message}
+	if f.rejectCh != nil {
+		f.rejectCh <- rejectCall{reason: reason, message: message}
+	}
 
 	return nil
 }
@@ -63,7 +73,9 @@
 }
 
 func TestUnknownChannelType(t *testing.T) {
-	rejectCh := make(chan rejectCall, 1)
+	rejectCh := make(chan rejectCall)
+	defer close(rejectCh)
+
 	newChannel := &fakeNewChannel{channelType: "unknown session", rejectCh: rejectCh}
 	conn, chans := setup(1, newChannel)
 
@@ -72,8 +84,64 @@
 	}()
 
 	rejectionData := <-rejectCh
-	close(rejectCh)
 
 	expectedRejection := rejectCall{reason: ssh.UnknownChannelType, message: "unknown channel type"}
 	require.Equal(t, expectedRejection, rejectionData)
 }
+
+func TestTooManySessions(t *testing.T) {
+	rejectCh := make(chan rejectCall)
+	defer close(rejectCh)
+
+	newChannel := &fakeNewChannel{channelType: "session", rejectCh: rejectCh}
+	conn, chans := setup(1, newChannel)
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	go func() {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+			<-ctx.Done() // Keep the accepted channel open until the end of the test
+		})
+	}()
+
+	chans <- newChannel
+	require.Equal(t, <-rejectCh, rejectCall{reason: ssh.ResourceShortage, message: "too many concurrent sessions"})
+}
+
+func TestAcceptSessionSucceeds(t *testing.T) {
+	newChannel := &fakeNewChannel{channelType: "session"}
+	conn, chans := setup(1, newChannel)
+
+	channelHandled := false
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		channelHandled = true
+		close(chans)
+	})
+
+	require.True(t, channelHandled)
+}
+
+func TestAcceptSessionFails(t *testing.T) {
+	acceptCh := make(chan struct{})
+	defer close(acceptCh)
+
+	acceptErr := errors.New("some failure")
+	newChannel := &fakeNewChannel{channelType: "session", acceptCh: acceptCh, acceptErr: acceptErr}
+	conn, chans := setup(1, newChannel)
+
+	channelHandled := false
+	go func() {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+			channelHandled = true
+		})
+	}()
+
+	require.Equal(t, <-acceptCh, struct{}{})
+
+	// Waits until the number of sessions is back to 0, since we can only have 1
+	conn.concurrentSessions.Acquire(context.Background(), 1)
+	defer conn.concurrentSessions.Release(1)
+
+	require.False(t, channelHandled)
+}
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1626948958 0
#      Thu Jul 22 10:15:58 2021 +0000
# Node ID 687e8fd1fae0939addf3d3297a25b2903e33c669
# Parent  23032cdc93aadbbdd6ded07c42637c58cfac13d2
# Parent  293e15251974026e8214c688c3d5c114691cdf86
Merge branch '521-test-connection-dot-go' into 'main'

Unit tests for internal/sshd/connection.go

Closes #521

See merge request gitlab-org/gitlab-shell!497

diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -2,6 +2,7 @@
 
 import (
 	"context"
+	"errors"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -9,22 +10,31 @@
 )
 
 type rejectCall struct {
-	reason ssh.RejectionReason
+	reason  ssh.RejectionReason
 	message string
 }
 
 type fakeNewChannel struct {
 	channelType string
 	extraData   []byte
+	acceptErr   error
+
+	acceptCh chan struct{}
 	rejectCh chan rejectCall
 }
 
 func (f *fakeNewChannel) Accept() (ssh.Channel, <-chan *ssh.Request, error) {
-	return nil, nil, nil
+	if f.acceptCh != nil {
+		f.acceptCh <- struct{}{}
+	}
+
+	return nil, nil, f.acceptErr
 }
 
 func (f *fakeNewChannel) Reject(reason ssh.RejectionReason, message string) error {
-	f.rejectCh <- rejectCall{reason: reason, message: message}
+	if f.rejectCh != nil {
+		f.rejectCh <- rejectCall{reason: reason, message: message}
+	}
 
 	return nil
 }
@@ -63,7 +73,9 @@
 }
 
 func TestUnknownChannelType(t *testing.T) {
-	rejectCh := make(chan rejectCall, 1)
+	rejectCh := make(chan rejectCall)
+	defer close(rejectCh)
+
 	newChannel := &fakeNewChannel{channelType: "unknown session", rejectCh: rejectCh}
 	conn, chans := setup(1, newChannel)
 
@@ -72,8 +84,64 @@
 	}()
 
 	rejectionData := <-rejectCh
-	close(rejectCh)
 
 	expectedRejection := rejectCall{reason: ssh.UnknownChannelType, message: "unknown channel type"}
 	require.Equal(t, expectedRejection, rejectionData)
 }
+
+func TestTooManySessions(t *testing.T) {
+	rejectCh := make(chan rejectCall)
+	defer close(rejectCh)
+
+	newChannel := &fakeNewChannel{channelType: "session", rejectCh: rejectCh}
+	conn, chans := setup(1, newChannel)
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	go func() {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+			<-ctx.Done() // Keep the accepted channel open until the end of the test
+		})
+	}()
+
+	chans <- newChannel
+	require.Equal(t, <-rejectCh, rejectCall{reason: ssh.ResourceShortage, message: "too many concurrent sessions"})
+}
+
+func TestAcceptSessionSucceeds(t *testing.T) {
+	newChannel := &fakeNewChannel{channelType: "session"}
+	conn, chans := setup(1, newChannel)
+
+	channelHandled := false
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		channelHandled = true
+		close(chans)
+	})
+
+	require.True(t, channelHandled)
+}
+
+func TestAcceptSessionFails(t *testing.T) {
+	acceptCh := make(chan struct{})
+	defer close(acceptCh)
+
+	acceptErr := errors.New("some failure")
+	newChannel := &fakeNewChannel{channelType: "session", acceptCh: acceptCh, acceptErr: acceptErr}
+	conn, chans := setup(1, newChannel)
+
+	channelHandled := false
+	go func() {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+			channelHandled = true
+		})
+	}()
+
+	require.Equal(t, <-acceptCh, struct{}{})
+
+	// Waits until the number of sessions is back to 0, since we can only have 1
+	conn.concurrentSessions.Acquire(context.Background(), 1)
+	defer conn.concurrentSessions.Release(1)
+
+	require.False(t, channelHandled)
+}
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1626965938 -10800
#      Thu Jul 22 17:58:58 2021 +0300
# Node ID c44839d4d44f0dabe53dba9cc7db0b4c12426f2e
# Parent  23032cdc93aadbbdd6ded07c42637c58cfac13d2
Switch to labkit/log for logging functionality

diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -11,9 +11,7 @@
 	"strings"
 	"time"
 
-	"gitlab.com/gitlab-org/labkit/correlation"
-
-	log "github.com/sirupsen/logrus"
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
@@ -71,12 +69,12 @@
 	return path
 }
 
-func newRequest(ctx context.Context, method, host, path string, data interface{}) (*http.Request, string, error) {
+func newRequest(ctx context.Context, method, host, path string, data interface{}) (*http.Request, error) {
 	var jsonReader io.Reader
 	if data != nil {
 		jsonData, err := json.Marshal(data)
 		if err != nil {
-			return nil, "", err
+			return nil, err
 		}
 
 		jsonReader = bytes.NewReader(jsonData)
@@ -84,12 +82,10 @@
 
 	request, err := http.NewRequestWithContext(ctx, method, host+path, jsonReader)
 	if err != nil {
-		return nil, "", err
+		return nil, err
 	}
 
-	correlationID := correlation.ExtractFromContext(ctx)
-
-	return request, correlationID, nil
+	return request, nil
 }
 
 func parseError(resp *http.Response) error {
@@ -116,7 +112,7 @@
 }
 
 func (c *GitlabNetClient) DoRequest(ctx context.Context, method, path string, data interface{}) (*http.Response, error) {
-	request, correlationID, err := newRequest(ctx, method, c.httpClient.Host, path, data)
+	request, err := newRequest(ctx, method, c.httpClient.Host, path, data)
 	if err != nil {
 		return nil, err
 	}
@@ -136,12 +132,11 @@
 	start := time.Now()
 	response, err := c.httpClient.Do(request)
 	fields := log.Fields{
-		"correlation_id": correlationID,
 		"method":         method,
 		"url":            request.URL.String(),
 		"duration_ms":    time.Since(start) / time.Millisecond,
 	}
-	logger := log.WithFields(fields)
+	logger := log.WithContextFields(ctx, fields)
 
 	if err != nil {
 		logger.WithError(err).Error("Internal API unreachable")
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -12,9 +12,9 @@
 	"strings"
 	"time"
 
-	log "github.com/sirupsen/logrus"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/tracing"
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -229,7 +229,7 @@
 	t.Cleanup(func() { pw.Close() })
 
 	scanner := bufio.NewScanner(pr)
-	extractor := regexp.MustCompile(`msg="Listening on ([0-9a-f\[\]\.:]+)"`)
+	extractor := regexp.MustCompile(`tcp_address="([0-9a-f\[\]\.:]+)"`)
 
 	ctx, cancel := context.WithCancel(context.Background())
 	cmd := exec.CommandContext(ctx, sshdPath, "-config-dir", dir)
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -8,13 +8,13 @@
 	"syscall"
 	"time"
 
-	log "github.com/sirupsen/logrus"
-
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshd"
+
 	"gitlab.com/gitlab-org/labkit/monitoring"
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 var (
@@ -49,15 +49,16 @@
 		var err error
 		cfg, err = config.NewFromDir(*configDir)
 		if err != nil {
-			log.Fatalf("failed to load configuration from specified directory: %v", err)
+			log.WithError(err).Fatal("failed to load configuration from specified directory")
 		}
 	}
 	overrideConfigFromEnvironment(cfg)
 	if err := cfg.IsSane(); err != nil {
 		if *configDir == "" {
-			log.Warn("note: no config-dir provided, using only environment variables")
+			log.WithError(err).Fatal("no config-dir provided, using only environment variables")
+		} else {
+			log.WithError(err).Fatal("configuration error")
 		}
-		log.Fatalf("configuration error: %v", err)
 	}
 
 	cfg.ApplyGlobalState()
@@ -72,13 +73,13 @@
 	// Startup monitoring endpoint.
 	if cfg.Server.WebListen != "" {
 		go func() {
-			log.Fatal(
-				monitoring.Start(
-					monitoring.WithListenerAddress(cfg.Server.WebListen),
-					monitoring.WithBuildInformation(Version, BuildTime),
-					monitoring.WithServeMux(server.MonitoringServeMux()),
-				),
+			err := monitoring.Start(
+				monitoring.WithListenerAddress(cfg.Server.WebListen),
+				monitoring.WithBuildInformation(Version, BuildTime),
+				monitoring.WithServeMux(server.MonitoringServeMux()),
 			)
+
+			log.WithError(err).Fatal("monitoring service raised an error")
 		}()
 	}
 
@@ -92,7 +93,7 @@
 		sig := <-done
 		signal.Reset(syscall.SIGINT, syscall.SIGTERM)
 
-		log.WithFields(log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Infof("Shutdown initiated")
+		log.WithFields(log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
 
 		server.Shutdown()
 
@@ -103,6 +104,6 @@
 	}()
 
 	if err := server.ListenAndServe(ctx); err != nil {
-		log.Fatalf("Failed to start GitLab built-in sshd: %v", err)
+		log.WithError(err).Fatal("Failed to start GitLab built-in sshd")
 	}
 }
diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -10,12 +10,13 @@
 	"io"
 	"net/http"
 
-	log "github.com/sirupsen/logrus"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/pktline"
+
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 type Request struct {
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -8,19 +8,20 @@
 
 	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
 	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
-	log "github.com/sirupsen/logrus"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/metadata"
 
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+
 	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/labkit/correlation"
 	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
 	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
+	"gitlab.com/gitlab-org/labkit/correlation"
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 // GitalyHandlerFunc implementations are responsible for making
@@ -75,7 +76,6 @@
 func (gc *GitalyCommand) LogExecution(ctx context.Context, repository *pb.Repository, response *accessverifier.Response, env sshenv.Env) {
 	fields := log.Fields{
 		"command":         gc.ServiceName,
-		"correlation_id":  correlation.ExtractFromContext(ctx),
 		"gl_project_path": repository.GlProjectPath,
 		"gl_repository":   repository.GlRepository,
 		"user_id":         response.UserId,
@@ -86,7 +86,7 @@
 		"gl_key_id":       response.KeyId,
 	}
 
-	log.WithFields(fields).Info("executing git command")
+	log.WithContextFields(ctx, fields).Info("executing git command")
 }
 
 func withOutgoingMetadata(ctx context.Context, features map[string]string) context.Context {
@@ -108,9 +108,9 @@
 
 	serviceName := correlation.ExtractClientNameFromContext(ctx)
 	if serviceName == "" {
-		log.Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
+		serviceName = "gitlab-shell-unknown"
 
-		serviceName = "gitlab-shell-unknown"
+		log.WithFields(log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
 	}
 
 	serviceName = fmt.Sprintf("%s-%s", serviceName, gc.ServiceName)
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -4,11 +4,12 @@
 	"context"
 	"time"
 
-	log "github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 type connection struct {
@@ -42,7 +43,7 @@
 		}
 		channel, requests, err := newChannel.Accept()
 		if err != nil {
-			log.Infof("Could not accept channel: %v", err)
+			log.WithError(err).Info("could not accept channel")
 			c.concurrentSessions.Release(1)
 			continue
 		}
@@ -53,7 +54,7 @@
 			// Prevent a panic in a single session from taking out the whole server
 			defer func() {
 				if err := recover(); err != nil {
-					log.Warnf("panic handling session from %s: recovered: %#+v", c.remoteAddr, err)
+					log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", c.remoteAddr)
 				}
 			}()
 
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -12,13 +12,13 @@
 	"sync"
 	"net/http"
 
-	log "github.com/sirupsen/logrus"
-
 	"github.com/pires/go-proxyproto"
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
+
+	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
@@ -89,7 +89,7 @@
 		log.Info("Proxy protocol is enabled")
 	}
 
-	log.Infof("Listening on %v", sshListener.Addr().String())
+	log.WithFields(log.Fields{"tcp_address": sshListener.Addr().String()}).Info("Listening for SSH connections")
 
 	s.listener = sshListener
 
@@ -111,7 +111,7 @@
 				break
 			}
 
-			log.Warnf("Failed to accept connection: %v\n", err)
+			log.WithError(err).Warn("Failed to accept connection")
 			continue
 		}
 
@@ -173,12 +173,12 @@
 	for _, filename := range s.Config.Server.HostKeyFiles {
 		keyRaw, err := ioutil.ReadFile(filename)
 		if err != nil {
-			log.Warnf("Failed to read host key %v: %v", filename, err)
+			log.WithError(err).Warnf("Failed to read host key %v", filename)
 			continue
 		}
 		key, err := ssh.ParsePrivateKey(keyRaw)
 		if err != nil {
-			log.Warnf("Failed to parse host key %v: %v", filename, err)
+			log.WithError(err).Warnf("Failed to parse host key %v", filename)
 			continue
 		}
 		loadedHostKeys++
@@ -201,7 +201,7 @@
 	// Prevent a panic in a single connection from taking out the whole server
 	defer func() {
 		if err := recover(); err != nil {
-			log.Warnf("panic handling connection from %s: recovered: %#+v", remoteAddr, err)
+			log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", remoteAddr)
 		}
 	}()
 
@@ -210,7 +210,7 @@
 
 	sconn, chans, reqs, err := ssh.NewServerConn(nconn, sshCfg)
 	if err != nil {
-		log.Infof("Failed to initialize SSH connection: %v", err)
+		log.WithError(err).Info("Failed to initialize SSH connection")
 		return
 	}
 
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1627014829 0
#      Fri Jul 23 04:33:49 2021 +0000
# Node ID 74469468b5ac973a2bc32ab92753eb218e6ad779
# Parent  687e8fd1fae0939addf3d3297a25b2903e33c669
# Parent  c44839d4d44f0dabe53dba9cc7db0b4c12426f2e
Merge branch 'id-switch-logging-to-labkit' into 'main'

Switch to labkit/log for logging functionality

See merge request gitlab-org/gitlab-shell!498

diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -11,9 +11,7 @@
 	"strings"
 	"time"
 
-	"gitlab.com/gitlab-org/labkit/correlation"
-
-	log "github.com/sirupsen/logrus"
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
@@ -71,12 +69,12 @@
 	return path
 }
 
-func newRequest(ctx context.Context, method, host, path string, data interface{}) (*http.Request, string, error) {
+func newRequest(ctx context.Context, method, host, path string, data interface{}) (*http.Request, error) {
 	var jsonReader io.Reader
 	if data != nil {
 		jsonData, err := json.Marshal(data)
 		if err != nil {
-			return nil, "", err
+			return nil, err
 		}
 
 		jsonReader = bytes.NewReader(jsonData)
@@ -84,12 +82,10 @@
 
 	request, err := http.NewRequestWithContext(ctx, method, host+path, jsonReader)
 	if err != nil {
-		return nil, "", err
+		return nil, err
 	}
 
-	correlationID := correlation.ExtractFromContext(ctx)
-
-	return request, correlationID, nil
+	return request, nil
 }
 
 func parseError(resp *http.Response) error {
@@ -116,7 +112,7 @@
 }
 
 func (c *GitlabNetClient) DoRequest(ctx context.Context, method, path string, data interface{}) (*http.Response, error) {
-	request, correlationID, err := newRequest(ctx, method, c.httpClient.Host, path, data)
+	request, err := newRequest(ctx, method, c.httpClient.Host, path, data)
 	if err != nil {
 		return nil, err
 	}
@@ -136,12 +132,11 @@
 	start := time.Now()
 	response, err := c.httpClient.Do(request)
 	fields := log.Fields{
-		"correlation_id": correlationID,
 		"method":         method,
 		"url":            request.URL.String(),
 		"duration_ms":    time.Since(start) / time.Millisecond,
 	}
-	logger := log.WithFields(fields)
+	logger := log.WithContextFields(ctx, fields)
 
 	if err != nil {
 		logger.WithError(err).Error("Internal API unreachable")
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -12,9 +12,9 @@
 	"strings"
 	"time"
 
-	log "github.com/sirupsen/logrus"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/tracing"
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -229,7 +229,7 @@
 	t.Cleanup(func() { pw.Close() })
 
 	scanner := bufio.NewScanner(pr)
-	extractor := regexp.MustCompile(`msg="Listening on ([0-9a-f\[\]\.:]+)"`)
+	extractor := regexp.MustCompile(`tcp_address="([0-9a-f\[\]\.:]+)"`)
 
 	ctx, cancel := context.WithCancel(context.Background())
 	cmd := exec.CommandContext(ctx, sshdPath, "-config-dir", dir)
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -8,13 +8,13 @@
 	"syscall"
 	"time"
 
-	log "github.com/sirupsen/logrus"
-
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshd"
+
 	"gitlab.com/gitlab-org/labkit/monitoring"
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 var (
@@ -49,15 +49,16 @@
 		var err error
 		cfg, err = config.NewFromDir(*configDir)
 		if err != nil {
-			log.Fatalf("failed to load configuration from specified directory: %v", err)
+			log.WithError(err).Fatal("failed to load configuration from specified directory")
 		}
 	}
 	overrideConfigFromEnvironment(cfg)
 	if err := cfg.IsSane(); err != nil {
 		if *configDir == "" {
-			log.Warn("note: no config-dir provided, using only environment variables")
+			log.WithError(err).Fatal("no config-dir provided, using only environment variables")
+		} else {
+			log.WithError(err).Fatal("configuration error")
 		}
-		log.Fatalf("configuration error: %v", err)
 	}
 
 	cfg.ApplyGlobalState()
@@ -72,13 +73,13 @@
 	// Startup monitoring endpoint.
 	if cfg.Server.WebListen != "" {
 		go func() {
-			log.Fatal(
-				monitoring.Start(
-					monitoring.WithListenerAddress(cfg.Server.WebListen),
-					monitoring.WithBuildInformation(Version, BuildTime),
-					monitoring.WithServeMux(server.MonitoringServeMux()),
-				),
+			err := monitoring.Start(
+				monitoring.WithListenerAddress(cfg.Server.WebListen),
+				monitoring.WithBuildInformation(Version, BuildTime),
+				monitoring.WithServeMux(server.MonitoringServeMux()),
 			)
+
+			log.WithError(err).Fatal("monitoring service raised an error")
 		}()
 	}
 
@@ -92,7 +93,7 @@
 		sig := <-done
 		signal.Reset(syscall.SIGINT, syscall.SIGTERM)
 
-		log.WithFields(log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Infof("Shutdown initiated")
+		log.WithFields(log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
 
 		server.Shutdown()
 
@@ -103,6 +104,6 @@
 	}()
 
 	if err := server.ListenAndServe(ctx); err != nil {
-		log.Fatalf("Failed to start GitLab built-in sshd: %v", err)
+		log.WithError(err).Fatal("Failed to start GitLab built-in sshd")
 	}
 }
diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -10,12 +10,13 @@
 	"io"
 	"net/http"
 
-	log "github.com/sirupsen/logrus"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/pktline"
+
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 type Request struct {
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -8,19 +8,20 @@
 
 	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
 	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
-	log "github.com/sirupsen/logrus"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/metadata"
 
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+
 	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/labkit/correlation"
 	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
 	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
+	"gitlab.com/gitlab-org/labkit/correlation"
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 // GitalyHandlerFunc implementations are responsible for making
@@ -75,7 +76,6 @@
 func (gc *GitalyCommand) LogExecution(ctx context.Context, repository *pb.Repository, response *accessverifier.Response, env sshenv.Env) {
 	fields := log.Fields{
 		"command":         gc.ServiceName,
-		"correlation_id":  correlation.ExtractFromContext(ctx),
 		"gl_project_path": repository.GlProjectPath,
 		"gl_repository":   repository.GlRepository,
 		"user_id":         response.UserId,
@@ -86,7 +86,7 @@
 		"gl_key_id":       response.KeyId,
 	}
 
-	log.WithFields(fields).Info("executing git command")
+	log.WithContextFields(ctx, fields).Info("executing git command")
 }
 
 func withOutgoingMetadata(ctx context.Context, features map[string]string) context.Context {
@@ -108,9 +108,9 @@
 
 	serviceName := correlation.ExtractClientNameFromContext(ctx)
 	if serviceName == "" {
-		log.Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
+		serviceName = "gitlab-shell-unknown"
 
-		serviceName = "gitlab-shell-unknown"
+		log.WithFields(log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
 	}
 
 	serviceName = fmt.Sprintf("%s-%s", serviceName, gc.ServiceName)
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -4,11 +4,12 @@
 	"context"
 	"time"
 
-	log "github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 type connection struct {
@@ -42,7 +43,7 @@
 		}
 		channel, requests, err := newChannel.Accept()
 		if err != nil {
-			log.Infof("Could not accept channel: %v", err)
+			log.WithError(err).Info("could not accept channel")
 			c.concurrentSessions.Release(1)
 			continue
 		}
@@ -53,7 +54,7 @@
 			// Prevent a panic in a single session from taking out the whole server
 			defer func() {
 				if err := recover(); err != nil {
-					log.Warnf("panic handling session from %s: recovered: %#+v", c.remoteAddr, err)
+					log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", c.remoteAddr)
 				}
 			}()
 
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -12,13 +12,13 @@
 	"sync"
 	"net/http"
 
-	log "github.com/sirupsen/logrus"
-
 	"github.com/pires/go-proxyproto"
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
+
+	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
@@ -89,7 +89,7 @@
 		log.Info("Proxy protocol is enabled")
 	}
 
-	log.Infof("Listening on %v", sshListener.Addr().String())
+	log.WithFields(log.Fields{"tcp_address": sshListener.Addr().String()}).Info("Listening for SSH connections")
 
 	s.listener = sshListener
 
@@ -111,7 +111,7 @@
 				break
 			}
 
-			log.Warnf("Failed to accept connection: %v\n", err)
+			log.WithError(err).Warn("Failed to accept connection")
 			continue
 		}
 
@@ -173,12 +173,12 @@
 	for _, filename := range s.Config.Server.HostKeyFiles {
 		keyRaw, err := ioutil.ReadFile(filename)
 		if err != nil {
-			log.Warnf("Failed to read host key %v: %v", filename, err)
+			log.WithError(err).Warnf("Failed to read host key %v", filename)
 			continue
 		}
 		key, err := ssh.ParsePrivateKey(keyRaw)
 		if err != nil {
-			log.Warnf("Failed to parse host key %v: %v", filename, err)
+			log.WithError(err).Warnf("Failed to parse host key %v", filename)
 			continue
 		}
 		loadedHostKeys++
@@ -201,7 +201,7 @@
 	// Prevent a panic in a single connection from taking out the whole server
 	defer func() {
 		if err := recover(); err != nil {
-			log.Warnf("panic handling connection from %s: recovered: %#+v", remoteAddr, err)
+			log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", remoteAddr)
 		}
 	}()
 
@@ -210,7 +210,7 @@
 
 	sconn, chans, reqs, err := ssh.NewServerConn(nconn, sshCfg)
 	if err != nil {
-		log.Infof("Failed to initialize SSH connection: %v", err)
+		log.WithError(err).Info("Failed to initialize SSH connection")
 		return
 	}
 
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1627341670 25200
#      Mon Jul 26 16:21:10 2021 -0700
# Node ID 4aa911586a1bdb878f30a4f2da1a80ea9f3f5934
# Parent  74469468b5ac973a2bc32ab92753eb218e6ad779
Make gofmt check fail if there are any matching files

gofmt doesn't return an exit code 1 if there are matching files:
https://github.com/golang/go/issues/24230

To fix this, use the same trick we use in Workhorse to parse output.
Also add a `make fmt` step to format all the code properly.

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,10 @@
 verify: verify_golang
 
 verify_golang:
-	gofmt -s -l $(GO_SOURCES)
+	gofmt -s -l $(GO_SOURCES) | awk '{ print } END { if (NR > 0) { print "Please run make fmt"; exit 1 } }'
+
+fmt:
+	gofmt -w -s $(GO_SOURCES)
 
 test: test_ruby test_golang
 
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1627341754 25200
#      Mon Jul 26 16:22:34 2021 -0700
# Node ID 445b4494c1da824e2d9c9488845c1fad1b42353f
# Parent  4aa911586a1bdb878f30a4f2da1a80ea9f3f5934
Fix formatting via make fmt

diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -132,9 +132,9 @@
 	start := time.Now()
 	response, err := c.httpClient.Do(request)
 	fields := log.Fields{
-		"method":         method,
-		"url":            request.URL.String(),
-		"duration_ms":    time.Since(start) / time.Millisecond,
+		"method":      method,
+		"url":         request.URL.String(),
+		"duration_ms": time.Since(start) / time.Millisecond,
 	}
 	logger := log.WithContextFields(ctx, fields)
 
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -13,8 +13,8 @@
 	"time"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
+	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/tracing"
-	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -1,10 +1,10 @@
 package main
 
 import (
+	"context"
 	"flag"
 	"os"
 	"os/signal"
-	"context"
 	"syscall"
 	"time"
 
@@ -13,8 +13,8 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshd"
 
+	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/monitoring"
-	"gitlab.com/gitlab-org/labkit/log"
 )
 
 var (
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -109,7 +109,6 @@
 		tr := client.Transport
 		client.Transport = promhttp.InstrumentRoundTripperDuration(metrics.HttpRequestDuration, tr)
 
-
 		c.httpClient = client
 	})
 
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -4,11 +4,11 @@
 	"os"
 	"testing"
 
+	"github.com/prometheus/client_golang/prometheus"
 	"github.com/stretchr/testify/require"
-	"github.com/prometheus/client_golang/prometheus"
 
+	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 )
 
 func TestConfigApplyGlobalState(t *testing.T) {
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -18,10 +18,10 @@
 	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/labkit/correlation"
 	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
+	"gitlab.com/gitlab-org/labkit/log"
 	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
-	"gitlab.com/gitlab-org/labkit/correlation"
-	"gitlab.com/gitlab-org/labkit/log"
 )
 
 // GitalyHandlerFunc implementations are responsible for making
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -7,10 +7,10 @@
 	"fmt"
 	"io/ioutil"
 	"net"
+	"net/http"
 	"strconv"
+	"sync"
 	"time"
-	"sync"
-	"net/http"
 
 	"github.com/pires/go-proxyproto"
 	"golang.org/x/crypto/ssh"
@@ -18,13 +18,13 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
 
+	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/log"
-	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
 type status int
 
-const(
+const (
 	StatusStarting status = iota
 	StatusReady
 	StatusOnShutdown
@@ -34,9 +34,9 @@
 type Server struct {
 	Config *config.Config
 
-	status status
+	status   status
 	statusMu sync.Mutex
-	wg sync.WaitGroup
+	wg       sync.WaitGroup
 	listener net.Listener
 }
 
@@ -71,7 +71,7 @@
 	})
 
 	mux.HandleFunc(s.Config.Server.LivenessProbe, func(w http.ResponseWriter, r *http.Request) {
-			w.WriteHeader(http.StatusOK)
+		w.WriteHeader(http.StatusOK)
 	})
 
 	return mux
@@ -191,7 +191,6 @@
 	return sshCfg, nil
 }
 
-
 func (s *Server) handleConn(ctx context.Context, sshCfg *ssh.ServerConfig, nconn net.Conn) {
 	remoteAddr := nconn.RemoteAddr().String()
 
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -1,10 +1,10 @@
 package sshd
 
 import (
+	"context"
+	"net/http/httptest"
+	"path"
 	"testing"
-	"context"
-	"path"
-	"net/http/httptest"
 	"time"
 
 	"github.com/stretchr/testify/require"
@@ -75,7 +75,7 @@
 
 	url := testserver.StartSocketHttpServer(t, []testserver.TestRequestHandler{})
 	srvCfg := config.ServerConfig{
-		Listen: serverUrl,
+		Listen:       serverUrl,
 		HostKeyFiles: []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")},
 	}
 
@@ -85,7 +85,7 @@
 }
 
 func verifyStatus(t *testing.T, s *Server, st status) {
-	for i := 5; i < 500; i+=50 {
+	for i := 5; i < 500; i += 50 {
 		if s.getStatus() == st {
 			break
 		}
diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go
--- a/internal/testhelper/testhelper.go
+++ b/internal/testhelper/testhelper.go
@@ -6,8 +6,8 @@
 	"os"
 	"path"
 	"runtime"
+	"testing"
 	"time"
-	"testing"
 
 	"github.com/otiai10/copy"
 	"github.com/sirupsen/logrus"
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1627378502 0
#      Tue Jul 27 09:35:02 2021 +0000
# Node ID da2664071061f9e28db0aa6287b3d1409d730d68
# Parent  74469468b5ac973a2bc32ab92753eb218e6ad779
# Parent  445b4494c1da824e2d9c9488845c1fad1b42353f
Merge branch 'sh-fix-gofmt' into 'main'

Make gofmt check fail if there are any matching files

See merge request gitlab-org/gitlab-shell!500

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,10 @@
 verify: verify_golang
 
 verify_golang:
-	gofmt -s -l $(GO_SOURCES)
+	gofmt -s -l $(GO_SOURCES) | awk '{ print } END { if (NR > 0) { print "Please run make fmt"; exit 1 } }'
+
+fmt:
+	gofmt -w -s $(GO_SOURCES)
 
 test: test_ruby test_golang
 
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -132,9 +132,9 @@
 	start := time.Now()
 	response, err := c.httpClient.Do(request)
 	fields := log.Fields{
-		"method":         method,
-		"url":            request.URL.String(),
-		"duration_ms":    time.Since(start) / time.Millisecond,
+		"method":      method,
+		"url":         request.URL.String(),
+		"duration_ms": time.Since(start) / time.Millisecond,
 	}
 	logger := log.WithContextFields(ctx, fields)
 
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -13,8 +13,8 @@
 	"time"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
+	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/tracing"
-	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -1,10 +1,10 @@
 package main
 
 import (
+	"context"
 	"flag"
 	"os"
 	"os/signal"
-	"context"
 	"syscall"
 	"time"
 
@@ -13,8 +13,8 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshd"
 
+	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/monitoring"
-	"gitlab.com/gitlab-org/labkit/log"
 )
 
 var (
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -109,7 +109,6 @@
 		tr := client.Transport
 		client.Transport = promhttp.InstrumentRoundTripperDuration(metrics.HttpRequestDuration, tr)
 
-
 		c.httpClient = client
 	})
 
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -4,11 +4,11 @@
 	"os"
 	"testing"
 
+	"github.com/prometheus/client_golang/prometheus"
 	"github.com/stretchr/testify/require"
-	"github.com/prometheus/client_golang/prometheus"
 
+	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 )
 
 func TestConfigApplyGlobalState(t *testing.T) {
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -18,10 +18,10 @@
 	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/labkit/correlation"
 	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
+	"gitlab.com/gitlab-org/labkit/log"
 	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
-	"gitlab.com/gitlab-org/labkit/correlation"
-	"gitlab.com/gitlab-org/labkit/log"
 )
 
 // GitalyHandlerFunc implementations are responsible for making
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -7,10 +7,10 @@
 	"fmt"
 	"io/ioutil"
 	"net"
+	"net/http"
 	"strconv"
+	"sync"
 	"time"
-	"sync"
-	"net/http"
 
 	"github.com/pires/go-proxyproto"
 	"golang.org/x/crypto/ssh"
@@ -18,13 +18,13 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
 
+	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/log"
-	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
 type status int
 
-const(
+const (
 	StatusStarting status = iota
 	StatusReady
 	StatusOnShutdown
@@ -34,9 +34,9 @@
 type Server struct {
 	Config *config.Config
 
-	status status
+	status   status
 	statusMu sync.Mutex
-	wg sync.WaitGroup
+	wg       sync.WaitGroup
 	listener net.Listener
 }
 
@@ -71,7 +71,7 @@
 	})
 
 	mux.HandleFunc(s.Config.Server.LivenessProbe, func(w http.ResponseWriter, r *http.Request) {
-			w.WriteHeader(http.StatusOK)
+		w.WriteHeader(http.StatusOK)
 	})
 
 	return mux
@@ -191,7 +191,6 @@
 	return sshCfg, nil
 }
 
-
 func (s *Server) handleConn(ctx context.Context, sshCfg *ssh.ServerConfig, nconn net.Conn) {
 	remoteAddr := nconn.RemoteAddr().String()
 
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -1,10 +1,10 @@
 package sshd
 
 import (
+	"context"
+	"net/http/httptest"
+	"path"
 	"testing"
-	"context"
-	"path"
-	"net/http/httptest"
 	"time"
 
 	"github.com/stretchr/testify/require"
@@ -75,7 +75,7 @@
 
 	url := testserver.StartSocketHttpServer(t, []testserver.TestRequestHandler{})
 	srvCfg := config.ServerConfig{
-		Listen: serverUrl,
+		Listen:       serverUrl,
 		HostKeyFiles: []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")},
 	}
 
@@ -85,7 +85,7 @@
 }
 
 func verifyStatus(t *testing.T, s *Server, st status) {
-	for i := 5; i < 500; i+=50 {
+	for i := 5; i < 500; i += 50 {
 		if s.getStatus() == st {
 			break
 		}
diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go
--- a/internal/testhelper/testhelper.go
+++ b/internal/testhelper/testhelper.go
@@ -6,8 +6,8 @@
 	"os"
 	"path"
 	"runtime"
+	"testing"
 	"time"
-	"testing"
 
 	"github.com/otiai10/copy"
 	"github.com/sirupsen/logrus"
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1627339379 25200
#      Mon Jul 26 15:42:59 2021 -0700
# Node ID 0db8e97c4aa95ca224d2dbaae7a03917ed886c32
# Parent  74469468b5ac973a2bc32ab92753eb218e6ad779
Update go-proxyproto to v0.6.0

From https://github.com/pires/go-proxyproto/releases:

Prevent potentially malicious client(s) from opening connections and not
send the proxy protocol header, which could lead to DoS as the server
would hold those socket descriptors open indefinitely, eventually
running out of resources. The solution is to set a read deadline when
waiting for the PROXY protocol header:
https://github.com/pires/go-proxyproto/pull/74

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@
 	github.com/mattn/go-shellwords v1.0.11
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
-	github.com/pires/go-proxyproto v0.5.0
+	github.com/pires/go-proxyproto v0.6.0
 	github.com/prometheus/client_golang v1.10.0
 	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.7.0
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -505,8 +505,8 @@
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
-github.com/pires/go-proxyproto v0.5.0 h1:A4Jv4ZCaV3AFJeGh5mGwkz4iuWUYMlQ7IoO/GTuSuLo=
-github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pires/go-proxyproto v0.6.0 h1:cLJUPnuQdiNf7P/wbeOKmM1khVdaMgTFDLj8h9ZrVYk=
+github.com/pires/go-proxyproto v0.6.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1627340061 25200
#      Mon Jul 26 15:54:21 2021 -0700
# Node ID 972fa21631f9058c9a028e65be82e07e385b4770
# Parent  0db8e97c4aa95ca224d2dbaae7a03917ed886c32
Set a 90-second timeout on proxy headers

diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -7,10 +7,10 @@
 	"fmt"
 	"io/ioutil"
 	"net"
+	"net/http"
 	"strconv"
+	"sync"
 	"time"
-	"sync"
-	"net/http"
 
 	"github.com/pires/go-proxyproto"
 	"golang.org/x/crypto/ssh"
@@ -18,25 +18,26 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
 
+	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/log"
-	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
 type status int
 
-const(
+const (
 	StatusStarting status = iota
 	StatusReady
 	StatusOnShutdown
 	StatusClosed
+	ProxyHeaderTimeout = 90 * time.Second
 )
 
 type Server struct {
 	Config *config.Config
 
-	status status
+	status   status
 	statusMu sync.Mutex
-	wg sync.WaitGroup
+	wg       sync.WaitGroup
 	listener net.Listener
 }
 
@@ -71,7 +72,7 @@
 	})
 
 	mux.HandleFunc(s.Config.Server.LivenessProbe, func(w http.ResponseWriter, r *http.Request) {
-			w.WriteHeader(http.StatusOK)
+		w.WriteHeader(http.StatusOK)
 	})
 
 	return mux
@@ -84,7 +85,10 @@
 	}
 
 	if s.Config.Server.ProxyProtocol {
-		sshListener = &proxyproto.Listener{Listener: sshListener}
+		sshListener = &proxyproto.Listener{
+			Listener:          sshListener,
+			ReadHeaderTimeout: ProxyHeaderTimeout,
+		}
 
 		log.Info("Proxy protocol is enabled")
 	}
@@ -191,7 +195,6 @@
 	return sshCfg, nil
 }
 
-
 func (s *Server) handleConn(ctx context.Context, sshCfg *ssh.ServerConfig, nconn net.Conn) {
 	remoteAddr := nconn.RemoteAddr().String()
 
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1627378654 0
#      Tue Jul 27 09:37:34 2021 +0000
# Node ID 4949e081af6690bc614873249604d13058084701
# Parent  da2664071061f9e28db0aa6287b3d1409d730d68
# Parent  972fa21631f9058c9a028e65be82e07e385b4770
Merge branch 'sh-update-go-proxyproto' into 'main'

Update go-proxyproto to v0.6.0

See merge request gitlab-org/gitlab-shell!499

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@
 	github.com/mattn/go-shellwords v1.0.11
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
-	github.com/pires/go-proxyproto v0.5.0
+	github.com/pires/go-proxyproto v0.6.0
 	github.com/prometheus/client_golang v1.10.0
 	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.7.0
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -505,8 +505,8 @@
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
-github.com/pires/go-proxyproto v0.5.0 h1:A4Jv4ZCaV3AFJeGh5mGwkz4iuWUYMlQ7IoO/GTuSuLo=
-github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pires/go-proxyproto v0.6.0 h1:cLJUPnuQdiNf7P/wbeOKmM1khVdaMgTFDLj8h9ZrVYk=
+github.com/pires/go-proxyproto v0.6.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -29,6 +29,7 @@
 	StatusReady
 	StatusOnShutdown
 	StatusClosed
+	ProxyHeaderTimeout = 90 * time.Second
 )
 
 type Server struct {
@@ -84,7 +85,10 @@
 	}
 
 	if s.Config.Server.ProxyProtocol {
-		sshListener = &proxyproto.Listener{Listener: sshListener}
+		sshListener = &proxyproto.Listener{
+			Listener:          sshListener,
+			ReadHeaderTimeout: ProxyHeaderTimeout,
+		}
 
 		log.Info("Proxy protocol is enabled")
 	}
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1627386765 -10800
#      Tue Jul 27 14:52:45 2021 +0300
# Node ID a126ea2cb7555f1cf1e7bb5d53fee1486fe2ae2d
# Parent  4949e081af6690bc614873249604d13058084701
Sshd: Log same correlation_id on auth keys

diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -68,7 +68,10 @@
 	ctx, finished := command.Setup("gitlab-sshd", cfg)
 	defer finished()
 
-	server := sshd.Server{Config: cfg}
+	server, err := sshd.NewServer(cfg)
+	if err != nil {
+		log.WithError(err).Fatal("Failed to start GitLab built-in sshd")
+	}
 
 	// Startup monitoring endpoint.
 	if cfg.Server.WebListen != "" {
@@ -104,6 +107,6 @@
 	}()
 
 	if err := server.ListenAndServe(ctx); err != nil {
-		log.WithError(err).Fatal("Failed to start GitLab built-in sshd")
+		log.WithError(err).Fatal("GitLab built-in sshd failed to listen for new connections")
 	}
 }
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -35,10 +35,40 @@
 type Server struct {
 	Config *config.Config
 
-	status   status
-	statusMu sync.Mutex
-	wg       sync.WaitGroup
-	listener net.Listener
+	status               status
+	statusMu             sync.Mutex
+	wg                   sync.WaitGroup
+	listener             net.Listener
+	hostKeys             []ssh.Signer
+	authorizedKeysClient *authorizedkeys.Client
+}
+
+func NewServer(cfg *config.Config) (*Server, error) {
+	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+	}
+
+	var hostKeys []ssh.Signer
+	for _, filename := range cfg.Server.HostKeyFiles {
+		keyRaw, err := ioutil.ReadFile(filename)
+		if err != nil {
+			log.WithError(err).Warnf("Failed to read host key %v", filename)
+			continue
+		}
+		key, err := ssh.ParsePrivateKey(keyRaw)
+		if err != nil {
+			log.WithError(err).Warnf("Failed to parse host key %v", filename)
+			continue
+		}
+
+		hostKeys = append(hostKeys, key)
+	}
+	if len(hostKeys) == 0 {
+		return nil, fmt.Errorf("No host keys could be loaded, aborting")
+	}
+
+	return &Server{Config: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
 }
 
 func (s *Server) ListenAndServe(ctx context.Context) error {
@@ -47,7 +77,9 @@
 	}
 	defer s.listener.Close()
 
-	return s.serve(ctx)
+	s.serve(ctx)
+
+	return nil
 }
 
 func (s *Server) Shutdown() error {
@@ -100,12 +132,7 @@
 	return nil
 }
 
-func (s *Server) serve(ctx context.Context) error {
-	sshCfg, err := s.initConfig(ctx)
-	if err != nil {
-		return err
-	}
-
+func (s *Server) serve(ctx context.Context) {
 	s.changeStatus(StatusReady)
 
 	for {
@@ -120,14 +147,12 @@
 		}
 
 		s.wg.Add(1)
-		go s.handleConn(ctx, sshCfg, nconn)
+		go s.handleConn(ctx, nconn)
 	}
 
 	s.wg.Wait()
 
 	s.changeStatus(StatusClosed)
-
-	return nil
 }
 
 func (s *Server) changeStatus(st status) {
@@ -143,12 +168,7 @@
 	return s.status
 }
 
-func (s *Server) initConfig(ctx context.Context) (*ssh.ServerConfig, error) {
-	authorizedKeysClient, err := authorizedkeys.NewClient(s.Config)
-	if err != nil {
-		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
-	}
-
+func (s *Server) serverConfig(ctx context.Context) *ssh.ServerConfig {
 	sshCfg := &ssh.ServerConfig{
 		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
 			if conn.User() != s.Config.User {
@@ -159,7 +179,7 @@
 			}
 			ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
 			defer cancel()
-			res, err := authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
+			res, err := s.authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
 			if err != nil {
 				return nil, err
 			}
@@ -173,29 +193,14 @@
 		},
 	}
 
-	var loadedHostKeys uint
-	for _, filename := range s.Config.Server.HostKeyFiles {
-		keyRaw, err := ioutil.ReadFile(filename)
-		if err != nil {
-			log.WithError(err).Warnf("Failed to read host key %v", filename)
-			continue
-		}
-		key, err := ssh.ParsePrivateKey(keyRaw)
-		if err != nil {
-			log.WithError(err).Warnf("Failed to parse host key %v", filename)
-			continue
-		}
-		loadedHostKeys++
+	for _, key := range s.hostKeys {
 		sshCfg.AddHostKey(key)
 	}
-	if loadedHostKeys == 0 {
-		return nil, fmt.Errorf("No host keys could be loaded, aborting")
-	}
 
-	return sshCfg, nil
+	return sshCfg
 }
 
-func (s *Server) handleConn(ctx context.Context, sshCfg *ssh.ServerConfig, nconn net.Conn) {
+func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
 	remoteAddr := nconn.RemoteAddr().String()
 
 	defer s.wg.Done()
@@ -211,7 +216,7 @@
 	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
 	defer cancel()
 
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, sshCfg)
+	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig(ctx))
 	if err != nil {
 		log.WithError(err).Info("Failed to initialize SSH connection")
 		return
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -2,37 +2,71 @@
 
 import (
 	"context"
+	"fmt"
+	"io/ioutil"
+	"net/http"
 	"net/http/httptest"
 	"path"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/require"
+	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
 
-const serverUrl = "127.0.0.1:50000"
+const (
+	serverUrl = "127.0.0.1:50000"
+	user      = "git"
+)
 
-func TestShutdown(t *testing.T) {
+var (
+	correlationId = ""
+)
+
+func TestListenAndServe(t *testing.T) {
 	s := setupServer(t)
 
-	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
-
-	verifyStatus(t, s, StatusReady)
-
-	s.wg.Add(1)
+	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.NoError(t, err)
+	defer client.Close()
 
 	require.NoError(t, s.Shutdown())
 	verifyStatus(t, s, StatusOnShutdown)
 
-	s.wg.Done()
+	holdSession(t, client)
+
+	_, err = ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.Equal(t, err.Error(), "dial tcp 127.0.0.1:50000: connect: connection refused")
+
+	client.Close()
 
 	verifyStatus(t, s, StatusClosed)
 }
 
+func TestCorrelationId(t *testing.T) {
+	setupServer(t)
+
+	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.NoError(t, err)
+	defer client.Close()
+
+	holdSession(t, client)
+
+	previousCorrelationId := correlationId
+
+	client, err = ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.NoError(t, err)
+	defer client.Close()
+
+	holdSession(t, client)
+
+	require.NotEqual(t, previousCorrelationId, correlationId)
+}
+
 func TestReadinessProbe(t *testing.T) {
 	s := &Server{Config: &config.Config{Server: config.DefaultServerConfig}}
 
@@ -71,17 +105,75 @@
 }
 
 func setupServer(t *testing.T) *Server {
+	t.Helper()
+
+	requests := []testserver.TestRequestHandler{
+		{
+			Path: "/api/v4/internal/authorized_keys",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				correlationId = r.Header.Get("X-Request-Id")
+
+				require.NotEmpty(t, correlationId)
+
+				fmt.Fprint(w, `{"id": 1000, "key": "key"}`)
+			},
+		}, {
+			Path: "/api/v4/internal/discover",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				require.Equal(t, correlationId, r.Header.Get("X-Request-Id"))
+
+				fmt.Fprint(w, `{"id": 1000, "name": "Test User", "username": "test-user"}`)
+			},
+		},
+	}
+
 	testhelper.PrepareTestRootDir(t)
 
-	url := testserver.StartSocketHttpServer(t, []testserver.TestRequestHandler{})
+	url := testserver.StartSocketHttpServer(t, requests)
 	srvCfg := config.ServerConfig{
-		Listen:       serverUrl,
-		HostKeyFiles: []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")},
+		Listen:                  serverUrl,
+		ConcurrentSessionsLimit: 1,
+		HostKeyFiles:            []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")},
 	}
 
-	cfg := &config.Config{RootDir: "/tmp", GitlabUrl: url, Server: srvCfg}
+	s, err := NewServer(&config.Config{User: user, RootDir: "/tmp", GitlabUrl: url, Server: srvCfg})
+	require.NoError(t, err)
+
+	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
+	t.Cleanup(func() { s.Shutdown() })
+
+	verifyStatus(t, s, StatusReady)
+
+	return s
+}
+
+func clientConfig(t *testing.T) *ssh.ClientConfig {
+	keyRaw, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server_authorized_key"))
+	pKey, _, _, _, err := ssh.ParseAuthorizedKey(keyRaw)
+	require.NoError(t, err)
 
-	return &Server{Config: cfg}
+	key, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/client/key.pem"))
+	require.NoError(t, err)
+	signer, err := ssh.ParsePrivateKey(key)
+	require.NoError(t, err)
+
+	return &ssh.ClientConfig{
+		User: user,
+		Auth: []ssh.AuthMethod{
+			ssh.PublicKeys(signer),
+		},
+		HostKeyCallback: ssh.FixedHostKey(pKey),
+	}
+}
+
+func holdSession(t *testing.T, c *ssh.Client) {
+	session, err := c.NewSession()
+	require.NoError(t, err)
+	defer session.Close()
+
+	output, err := session.Output("discover")
+	require.NoError(t, err)
+	require.Equal(t, "Welcome to GitLab, @test-user!\n", string(output))
 }
 
 func verifyStatus(t *testing.T, s *Server, st status) {
@@ -94,5 +186,5 @@
 		time.Sleep(time.Duration(i) * time.Millisecond)
 	}
 
-	require.Equal(t, s.getStatus(), st)
+	require.Equal(t, st, s.getStatus())
 }
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server_authorized_key b/internal/testhelper/testdata/testroot/certs/valid/server_authorized_key
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server_authorized_key
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yql
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1627400514 0
#      Tue Jul 27 15:41:54 2021 +0000
# Node ID 84b7234ba7d9caf04353ea64f82b77e8c6dfc14a
# Parent  4949e081af6690bc614873249604d13058084701
# Parent  a126ea2cb7555f1cf1e7bb5d53fee1486fe2ae2d
Merge branch 'id-ctx-for-auth-check' into 'main'

Log same correlation_id on auth keys check of ssh connections

See merge request gitlab-org/gitlab-shell!501

diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -68,7 +68,10 @@
 	ctx, finished := command.Setup("gitlab-sshd", cfg)
 	defer finished()
 
-	server := sshd.Server{Config: cfg}
+	server, err := sshd.NewServer(cfg)
+	if err != nil {
+		log.WithError(err).Fatal("Failed to start GitLab built-in sshd")
+	}
 
 	// Startup monitoring endpoint.
 	if cfg.Server.WebListen != "" {
@@ -104,6 +107,6 @@
 	}()
 
 	if err := server.ListenAndServe(ctx); err != nil {
-		log.WithError(err).Fatal("Failed to start GitLab built-in sshd")
+		log.WithError(err).Fatal("GitLab built-in sshd failed to listen for new connections")
 	}
 }
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -35,10 +35,40 @@
 type Server struct {
 	Config *config.Config
 
-	status   status
-	statusMu sync.Mutex
-	wg       sync.WaitGroup
-	listener net.Listener
+	status               status
+	statusMu             sync.Mutex
+	wg                   sync.WaitGroup
+	listener             net.Listener
+	hostKeys             []ssh.Signer
+	authorizedKeysClient *authorizedkeys.Client
+}
+
+func NewServer(cfg *config.Config) (*Server, error) {
+	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+	}
+
+	var hostKeys []ssh.Signer
+	for _, filename := range cfg.Server.HostKeyFiles {
+		keyRaw, err := ioutil.ReadFile(filename)
+		if err != nil {
+			log.WithError(err).Warnf("Failed to read host key %v", filename)
+			continue
+		}
+		key, err := ssh.ParsePrivateKey(keyRaw)
+		if err != nil {
+			log.WithError(err).Warnf("Failed to parse host key %v", filename)
+			continue
+		}
+
+		hostKeys = append(hostKeys, key)
+	}
+	if len(hostKeys) == 0 {
+		return nil, fmt.Errorf("No host keys could be loaded, aborting")
+	}
+
+	return &Server{Config: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
 }
 
 func (s *Server) ListenAndServe(ctx context.Context) error {
@@ -47,7 +77,9 @@
 	}
 	defer s.listener.Close()
 
-	return s.serve(ctx)
+	s.serve(ctx)
+
+	return nil
 }
 
 func (s *Server) Shutdown() error {
@@ -100,12 +132,7 @@
 	return nil
 }
 
-func (s *Server) serve(ctx context.Context) error {
-	sshCfg, err := s.initConfig(ctx)
-	if err != nil {
-		return err
-	}
-
+func (s *Server) serve(ctx context.Context) {
 	s.changeStatus(StatusReady)
 
 	for {
@@ -120,14 +147,12 @@
 		}
 
 		s.wg.Add(1)
-		go s.handleConn(ctx, sshCfg, nconn)
+		go s.handleConn(ctx, nconn)
 	}
 
 	s.wg.Wait()
 
 	s.changeStatus(StatusClosed)
-
-	return nil
 }
 
 func (s *Server) changeStatus(st status) {
@@ -143,12 +168,7 @@
 	return s.status
 }
 
-func (s *Server) initConfig(ctx context.Context) (*ssh.ServerConfig, error) {
-	authorizedKeysClient, err := authorizedkeys.NewClient(s.Config)
-	if err != nil {
-		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
-	}
-
+func (s *Server) serverConfig(ctx context.Context) *ssh.ServerConfig {
 	sshCfg := &ssh.ServerConfig{
 		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
 			if conn.User() != s.Config.User {
@@ -159,7 +179,7 @@
 			}
 			ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
 			defer cancel()
-			res, err := authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
+			res, err := s.authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
 			if err != nil {
 				return nil, err
 			}
@@ -173,29 +193,14 @@
 		},
 	}
 
-	var loadedHostKeys uint
-	for _, filename := range s.Config.Server.HostKeyFiles {
-		keyRaw, err := ioutil.ReadFile(filename)
-		if err != nil {
-			log.WithError(err).Warnf("Failed to read host key %v", filename)
-			continue
-		}
-		key, err := ssh.ParsePrivateKey(keyRaw)
-		if err != nil {
-			log.WithError(err).Warnf("Failed to parse host key %v", filename)
-			continue
-		}
-		loadedHostKeys++
+	for _, key := range s.hostKeys {
 		sshCfg.AddHostKey(key)
 	}
-	if loadedHostKeys == 0 {
-		return nil, fmt.Errorf("No host keys could be loaded, aborting")
-	}
 
-	return sshCfg, nil
+	return sshCfg
 }
 
-func (s *Server) handleConn(ctx context.Context, sshCfg *ssh.ServerConfig, nconn net.Conn) {
+func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
 	remoteAddr := nconn.RemoteAddr().String()
 
 	defer s.wg.Done()
@@ -211,7 +216,7 @@
 	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
 	defer cancel()
 
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, sshCfg)
+	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig(ctx))
 	if err != nil {
 		log.WithError(err).Info("Failed to initialize SSH connection")
 		return
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -2,37 +2,71 @@
 
 import (
 	"context"
+	"fmt"
+	"io/ioutil"
+	"net/http"
 	"net/http/httptest"
 	"path"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/require"
+	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
 
-const serverUrl = "127.0.0.1:50000"
+const (
+	serverUrl = "127.0.0.1:50000"
+	user      = "git"
+)
 
-func TestShutdown(t *testing.T) {
+var (
+	correlationId = ""
+)
+
+func TestListenAndServe(t *testing.T) {
 	s := setupServer(t)
 
-	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
-
-	verifyStatus(t, s, StatusReady)
-
-	s.wg.Add(1)
+	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.NoError(t, err)
+	defer client.Close()
 
 	require.NoError(t, s.Shutdown())
 	verifyStatus(t, s, StatusOnShutdown)
 
-	s.wg.Done()
+	holdSession(t, client)
+
+	_, err = ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.Equal(t, err.Error(), "dial tcp 127.0.0.1:50000: connect: connection refused")
+
+	client.Close()
 
 	verifyStatus(t, s, StatusClosed)
 }
 
+func TestCorrelationId(t *testing.T) {
+	setupServer(t)
+
+	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.NoError(t, err)
+	defer client.Close()
+
+	holdSession(t, client)
+
+	previousCorrelationId := correlationId
+
+	client, err = ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.NoError(t, err)
+	defer client.Close()
+
+	holdSession(t, client)
+
+	require.NotEqual(t, previousCorrelationId, correlationId)
+}
+
 func TestReadinessProbe(t *testing.T) {
 	s := &Server{Config: &config.Config{Server: config.DefaultServerConfig}}
 
@@ -71,17 +105,75 @@
 }
 
 func setupServer(t *testing.T) *Server {
+	t.Helper()
+
+	requests := []testserver.TestRequestHandler{
+		{
+			Path: "/api/v4/internal/authorized_keys",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				correlationId = r.Header.Get("X-Request-Id")
+
+				require.NotEmpty(t, correlationId)
+
+				fmt.Fprint(w, `{"id": 1000, "key": "key"}`)
+			},
+		}, {
+			Path: "/api/v4/internal/discover",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				require.Equal(t, correlationId, r.Header.Get("X-Request-Id"))
+
+				fmt.Fprint(w, `{"id": 1000, "name": "Test User", "username": "test-user"}`)
+			},
+		},
+	}
+
 	testhelper.PrepareTestRootDir(t)
 
-	url := testserver.StartSocketHttpServer(t, []testserver.TestRequestHandler{})
+	url := testserver.StartSocketHttpServer(t, requests)
 	srvCfg := config.ServerConfig{
-		Listen:       serverUrl,
-		HostKeyFiles: []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")},
+		Listen:                  serverUrl,
+		ConcurrentSessionsLimit: 1,
+		HostKeyFiles:            []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")},
 	}
 
-	cfg := &config.Config{RootDir: "/tmp", GitlabUrl: url, Server: srvCfg}
+	s, err := NewServer(&config.Config{User: user, RootDir: "/tmp", GitlabUrl: url, Server: srvCfg})
+	require.NoError(t, err)
+
+	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
+	t.Cleanup(func() { s.Shutdown() })
+
+	verifyStatus(t, s, StatusReady)
+
+	return s
+}
+
+func clientConfig(t *testing.T) *ssh.ClientConfig {
+	keyRaw, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server_authorized_key"))
+	pKey, _, _, _, err := ssh.ParseAuthorizedKey(keyRaw)
+	require.NoError(t, err)
 
-	return &Server{Config: cfg}
+	key, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/client/key.pem"))
+	require.NoError(t, err)
+	signer, err := ssh.ParsePrivateKey(key)
+	require.NoError(t, err)
+
+	return &ssh.ClientConfig{
+		User: user,
+		Auth: []ssh.AuthMethod{
+			ssh.PublicKeys(signer),
+		},
+		HostKeyCallback: ssh.FixedHostKey(pKey),
+	}
+}
+
+func holdSession(t *testing.T, c *ssh.Client) {
+	session, err := c.NewSession()
+	require.NoError(t, err)
+	defer session.Close()
+
+	output, err := session.Output("discover")
+	require.NoError(t, err)
+	require.Equal(t, "Welcome to GitLab, @test-user!\n", string(output))
 }
 
 func verifyStatus(t *testing.T, s *Server, st status) {
@@ -94,5 +186,5 @@
 		time.Sleep(time.Duration(i) * time.Millisecond)
 	}
 
-	require.Equal(t, s.getStatus(), st)
+	require.Equal(t, st, s.getStatus())
 }
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server_authorized_key b/internal/testhelper/testdata/testroot/certs/valid/server_authorized_key
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server_authorized_key
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yql
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1627657747 -3600
#      Fri Jul 30 16:09:07 2021 +0100
# Node ID d8a447645e047a33945a38b0fa64a8fe53cba5ff
# Parent  84b7234ba7d9caf04353ea64f82b77e8c6dfc14a
Remove some unreliable tests

Logrus buffers its output internally, which makes these tests fail
intermittently. They're also not a good example to follow generally.

We now have acceptance tests that exercise this functionality so I'm
pretty relaxed about losing the expectations. However, we can test
them by inspecting the server-received metadata too, so there's no loss
of coverage here.

The move from logrus to labkit for logging also makes these tests hard
to justify keeping.

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -11,8 +11,8 @@
 	"strings"
 	"testing"
 
-	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
+
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
@@ -76,7 +76,6 @@
 
 func testSuccessfulGet(t *testing.T, client *GitlabNetClient) {
 	t.Run("Successful get", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		response, err := client.Get(context.Background(), "/hello")
 		require.NoError(t, err)
 		require.NotNil(t, response)
@@ -86,22 +85,11 @@
 		responseBody, err := ioutil.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, string(responseBody), "Hello")
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=GET")
-		require.Contains(t, entries[0].Message, "status=200")
-		require.Contains(t, entries[0].Message, "content_length_bytes=")
-		require.Contains(t, entries[0].Message, "Finished HTTP request")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
 func testSuccessfulPost(t *testing.T, client *GitlabNetClient) {
 	t.Run("Successful Post", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		data := map[string]string{"key": "value"}
 
 		response, err := client.Post(context.Background(), "/post_endpoint", data)
@@ -113,50 +101,20 @@
 		responseBody, err := ioutil.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, "Echo: {\"key\":\"value\"}", string(responseBody))
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=POST")
-		require.Contains(t, entries[0].Message, "status=200")
-		require.Contains(t, entries[0].Message, "content_length_bytes=")
-		require.Contains(t, entries[0].Message, "Finished HTTP request")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
 func testMissing(t *testing.T, client *GitlabNetClient) {
 	t.Run("Missing error for GET", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		response, err := client.Get(context.Background(), "/missing")
 		require.EqualError(t, err, "Internal API error (404)")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=GET")
-		require.Contains(t, entries[0].Message, "status=404")
-		require.Contains(t, entries[0].Message, "Internal API error")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 
 	t.Run("Missing error for POST", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		response, err := client.Post(context.Background(), "/missing", map[string]string{})
 		require.EqualError(t, err, "Internal API error (404)")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=POST")
-		require.Contains(t, entries[0].Message, "status=404")
-		require.Contains(t, entries[0].Message, "Internal API error")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
@@ -176,37 +134,15 @@
 
 func testBrokenRequest(t *testing.T, client *GitlabNetClient) {
 	t.Run("Broken request for GET", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
-
 		response, err := client.Get(context.Background(), "/broken")
 		require.EqualError(t, err, "Internal API unreachable")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=GET")
-		require.NotContains(t, entries[0].Message, "status=")
-		require.Contains(t, entries[0].Message, "Internal API unreachable")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 
 	t.Run("Broken request for POST", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
-
 		response, err := client.Post(context.Background(), "/broken", map[string]string{})
 		require.EqualError(t, err, "Internal API unreachable")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=POST")
-		require.NotContains(t, entries[0].Message, "status=")
-		require.Contains(t, entries[0].Message, "Internal API unreachable")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
diff --git a/internal/command/receivepack/gitalycall_test.go b/internal/command/receivepack/gitalycall_test.go
--- a/internal/command/receivepack/gitalycall_test.go
+++ b/internal/command/receivepack/gitalycall_test.go
@@ -5,7 +5,6 @@
 	"context"
 	"testing"
 
-	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
@@ -14,12 +13,11 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
 )
 
 func TestReceivePack(t *testing.T) {
-	gitalyAddress, _ := testserver.StartGitalyServer(t)
+	gitalyAddress, testServer := testserver.StartGitalyServer(t)
 
 	requests := requesthandlers.BuildAllowedWithGitalyHandlers(t, gitalyAddress)
 	url := testserver.StartHttpServer(t, requests)
@@ -43,10 +41,15 @@
 
 		env := sshenv.Env{
 			IsSSHConnection: true,
-			OriginalCommand: "git-receive-pack group/repo",
+			OriginalCommand: "git-receive-pack " + repo,
 			RemoteAddr:      "127.0.0.1",
 		}
-		args := &commandargs.Shell{CommandType: commandargs.ReceivePack, SshArgs: []string{"git-receive-pack", repo}, Env: env}
+
+		args := &commandargs.Shell{
+			CommandType: commandargs.ReceivePack,
+			SshArgs:     []string{"git-receive-pack", repo},
+			Env:         env,
+		}
 
 		if tc.username != "" {
 			args.GitlabUsername = tc.username
@@ -60,7 +63,6 @@
 			ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 		}
 
-		hook := testhelper.SetupLogger()
 		ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 		ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
@@ -73,15 +75,20 @@
 			require.Equal(t, "ReceivePack: key-123 "+repo, output.String())
 		}
 
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 2, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[1].Level)
-		require.Contains(t, entries[1].Message, "executing git command")
-		require.Contains(t, entries[1].Message, "command=git-receive-pack")
-		require.Contains(t, entries[1].Message, "remote_ip=127.0.0.1")
-		require.Contains(t, entries[1].Message, "gl_key_type=key")
-		require.Contains(t, entries[1].Message, "gl_key_id=123")
-		require.Contains(t, entries[1].Message, "correlation_id=a-correlation-id")
+		for k, v := range map[string]string{
+			"gitaly-feature-cache_invalidator":        "true",
+			"gitaly-feature-inforef_uploadpack_cache": "false",
+			"x-gitlab-client-name":                    "gitlab-shell-tests-git-receive-pack",
+			"key_id":                                  "123",
+			"user_id":                                 "1",
+			"remote_ip":                               "127.0.0.1",
+			"key_type":                                "key",
+		} {
+			actual := testServer.ReceivedMD[k]
+			require.Len(t, actual, 1)
+			require.Equal(t, v, actual[0])
+		}
+		require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+		require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 	}
 }
diff --git a/internal/command/uploadarchive/gitalycall_test.go b/internal/command/uploadarchive/gitalycall_test.go
--- a/internal/command/uploadarchive/gitalycall_test.go
+++ b/internal/command/uploadarchive/gitalycall_test.go
@@ -5,7 +5,6 @@
 	"context"
 	"testing"
 
-	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
@@ -13,12 +12,12 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
 )
 
-func TestUploadPack(t *testing.T) {
-	gitalyAddress, _ := testserver.StartGitalyServer(t)
+func TestUploadArchive(t *testing.T) {
+	gitalyAddress, testServer := testserver.StartGitalyServer(t)
 
 	requests := requesthandlers.BuildAllowedWithGitalyHandlers(t, gitalyAddress)
 	url := testserver.StartHttpServer(t, requests)
@@ -29,13 +28,25 @@
 	userId := "1"
 	repo := "group/repo"
 
+	env := sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: "git-upload-archive " + repo,
+		RemoteAddr:      "127.0.0.1",
+	}
+
+	args := &commandargs.Shell{
+		GitlabKeyId: userId,
+		CommandType: commandargs.UploadArchive,
+		SshArgs:     []string{"git-upload-archive", repo},
+		Env:         env,
+	}
+
 	cmd := &Command{
 		Config:     &config.Config{GitlabUrl: url},
-		Args:       &commandargs.Shell{GitlabKeyId: userId, CommandType: commandargs.UploadArchive, SshArgs: []string{"git-upload-archive", repo}},
+		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
 
-	hook := testhelper.SetupLogger()
 	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
@@ -44,13 +55,19 @@
 
 	require.Equal(t, "UploadArchive: "+repo, output.String())
 
-	require.True(t, testhelper.WaitForLogEvent(hook))
-	entries := hook.AllEntries()
-	require.Equal(t, 2, len(entries))
-	require.Equal(t, logrus.InfoLevel, entries[1].Level)
-	require.Contains(t, entries[1].Message, "executing git command")
-	require.Contains(t, entries[1].Message, "command=git-upload-archive")
-	require.Contains(t, entries[1].Message, "gl_key_type=key")
-	require.Contains(t, entries[1].Message, "gl_key_id=123")
-	require.Contains(t, entries[1].Message, "correlation_id=a-correlation-id")
+	for k, v := range map[string]string{
+		"gitaly-feature-cache_invalidator":        "true",
+		"gitaly-feature-inforef_uploadpack_cache": "false",
+		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-archive",
+		"key_id":                                  "123",
+		"user_id":                                 "1",
+		"remote_ip":                               "127.0.0.1",
+		"key_type":                                "key",
+	} {
+		actual := testServer.ReceivedMD[k]
+		require.Len(t, actual, 1)
+		require.Equal(t, v, actual[0])
+	}
+	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 }
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -4,7 +4,6 @@
 	"bytes"
 	"context"
 	"testing"
-	"time"
 
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
@@ -13,7 +12,7 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
 )
 
@@ -29,13 +28,25 @@
 	userId := "1"
 	repo := "group/repo"
 
+	env := sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: "git-upload-pack " + repo,
+		RemoteAddr:      "127.0.0.1",
+	}
+
+	args := &commandargs.Shell{
+		GitlabKeyId: userId,
+		CommandType: commandargs.UploadPack,
+		SshArgs:     []string{"git-upload-pack", repo},
+		Env:         env,
+	}
+
 	cmd := &Command{
 		Config:     &config.Config{GitlabUrl: url},
-		Args:       &commandargs.Shell{GitlabKeyId: userId, CommandType: commandargs.UploadPack, SshArgs: []string{"git-upload-pack", repo}},
+		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
 
-	hook := testhelper.SetupLogger()
 	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
@@ -43,25 +54,20 @@
 	require.NoError(t, err)
 
 	require.Equal(t, "UploadPack: "+repo, output.String())
-	require.Eventually(t, func() bool {
-		entries := hook.AllEntries()
-
-		require.Equal(t, 2, len(entries))
-		require.Contains(t, entries[1].Message, "executing git command")
-		require.Contains(t, entries[1].Message, "command=git-upload-pack")
-		require.Contains(t, entries[1].Message, "gl_key_type=key")
-		require.Contains(t, entries[1].Message, "gl_key_id=123")
-		require.Contains(t, entries[1].Message, "correlation_id=a-correlation-id")
-		return true
-	}, time.Second, time.Millisecond)
 
 	for k, v := range map[string]string{
 		"gitaly-feature-cache_invalidator":        "true",
 		"gitaly-feature-inforef_uploadpack_cache": "false",
+		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-pack",
+		"key_id":                                  "123",
+		"user_id":                                 "1",
+		"remote_ip":                               "127.0.0.1",
+		"key_type":                                "key",
 	} {
 		actual := testServer.ReceivedMD[k]
 		require.Len(t, actual, 1)
 		require.Equal(t, v, actual[0])
 	}
 	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 }
diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go
--- a/internal/testhelper/testhelper.go
+++ b/internal/testhelper/testhelper.go
@@ -7,11 +7,8 @@
 	"path"
 	"runtime"
 	"testing"
-	"time"
 
 	"github.com/otiai10/copy"
-	"github.com/sirupsen/logrus"
-	"github.com/sirupsen/logrus/hooks/test"
 	"github.com/stretchr/testify/require"
 )
 
@@ -75,25 +72,3 @@
 	err := os.Setenv(key, value)
 	return func() { os.Setenv(key, oldValue) }, err
 }
-
-func SetupLogger() *test.Hook {
-	logger, hook := test.NewNullLogger()
-	logrus.SetOutput(logger.Writer())
-
-	return hook
-}
-
-// logrus fires a Goroutine to write the output log, but there's no way to
-// flush all outstanding hooks to fire. We just wait up to a second
-// for an event to appear.
-func WaitForLogEvent(hook *test.Hook) bool {
-	for i := 0; i < 10; i++ {
-		if entry := hook.LastEntry(); entry != nil {
-			return true
-		}
-
-		time.Sleep(100 * time.Millisecond)
-	}
-
-	return false
-}
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1627881433 0
#      Mon Aug 02 05:17:13 2021 +0000
# Node ID 13bc78f1f276a64b306d4d26ea67239804c8b634
# Parent  84b7234ba7d9caf04353ea64f82b77e8c6dfc14a
# Parent  d8a447645e047a33945a38b0fa64a8fe53cba5ff
Merge branch '477-remove-logrus-tests' into 'main'

Remove some unreliable tests

Closes #477

See merge request gitlab-org/gitlab-shell!503

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -11,8 +11,8 @@
 	"strings"
 	"testing"
 
-	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
+
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
@@ -76,7 +76,6 @@
 
 func testSuccessfulGet(t *testing.T, client *GitlabNetClient) {
 	t.Run("Successful get", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		response, err := client.Get(context.Background(), "/hello")
 		require.NoError(t, err)
 		require.NotNil(t, response)
@@ -86,22 +85,11 @@
 		responseBody, err := ioutil.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, string(responseBody), "Hello")
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=GET")
-		require.Contains(t, entries[0].Message, "status=200")
-		require.Contains(t, entries[0].Message, "content_length_bytes=")
-		require.Contains(t, entries[0].Message, "Finished HTTP request")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
 func testSuccessfulPost(t *testing.T, client *GitlabNetClient) {
 	t.Run("Successful Post", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		data := map[string]string{"key": "value"}
 
 		response, err := client.Post(context.Background(), "/post_endpoint", data)
@@ -113,50 +101,20 @@
 		responseBody, err := ioutil.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, "Echo: {\"key\":\"value\"}", string(responseBody))
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=POST")
-		require.Contains(t, entries[0].Message, "status=200")
-		require.Contains(t, entries[0].Message, "content_length_bytes=")
-		require.Contains(t, entries[0].Message, "Finished HTTP request")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
 func testMissing(t *testing.T, client *GitlabNetClient) {
 	t.Run("Missing error for GET", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		response, err := client.Get(context.Background(), "/missing")
 		require.EqualError(t, err, "Internal API error (404)")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=GET")
-		require.Contains(t, entries[0].Message, "status=404")
-		require.Contains(t, entries[0].Message, "Internal API error")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 
 	t.Run("Missing error for POST", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		response, err := client.Post(context.Background(), "/missing", map[string]string{})
 		require.EqualError(t, err, "Internal API error (404)")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=POST")
-		require.Contains(t, entries[0].Message, "status=404")
-		require.Contains(t, entries[0].Message, "Internal API error")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
@@ -176,37 +134,15 @@
 
 func testBrokenRequest(t *testing.T, client *GitlabNetClient) {
 	t.Run("Broken request for GET", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
-
 		response, err := client.Get(context.Background(), "/broken")
 		require.EqualError(t, err, "Internal API unreachable")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=GET")
-		require.NotContains(t, entries[0].Message, "status=")
-		require.Contains(t, entries[0].Message, "Internal API unreachable")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 
 	t.Run("Broken request for POST", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
-
 		response, err := client.Post(context.Background(), "/broken", map[string]string{})
 		require.EqualError(t, err, "Internal API unreachable")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=POST")
-		require.NotContains(t, entries[0].Message, "status=")
-		require.Contains(t, entries[0].Message, "Internal API unreachable")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
diff --git a/internal/command/receivepack/gitalycall_test.go b/internal/command/receivepack/gitalycall_test.go
--- a/internal/command/receivepack/gitalycall_test.go
+++ b/internal/command/receivepack/gitalycall_test.go
@@ -5,7 +5,6 @@
 	"context"
 	"testing"
 
-	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
@@ -14,12 +13,11 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
 )
 
 func TestReceivePack(t *testing.T) {
-	gitalyAddress, _ := testserver.StartGitalyServer(t)
+	gitalyAddress, testServer := testserver.StartGitalyServer(t)
 
 	requests := requesthandlers.BuildAllowedWithGitalyHandlers(t, gitalyAddress)
 	url := testserver.StartHttpServer(t, requests)
@@ -43,10 +41,15 @@
 
 		env := sshenv.Env{
 			IsSSHConnection: true,
-			OriginalCommand: "git-receive-pack group/repo",
+			OriginalCommand: "git-receive-pack " + repo,
 			RemoteAddr:      "127.0.0.1",
 		}
-		args := &commandargs.Shell{CommandType: commandargs.ReceivePack, SshArgs: []string{"git-receive-pack", repo}, Env: env}
+
+		args := &commandargs.Shell{
+			CommandType: commandargs.ReceivePack,
+			SshArgs:     []string{"git-receive-pack", repo},
+			Env:         env,
+		}
 
 		if tc.username != "" {
 			args.GitlabUsername = tc.username
@@ -60,7 +63,6 @@
 			ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 		}
 
-		hook := testhelper.SetupLogger()
 		ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 		ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
@@ -73,15 +75,20 @@
 			require.Equal(t, "ReceivePack: key-123 "+repo, output.String())
 		}
 
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 2, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[1].Level)
-		require.Contains(t, entries[1].Message, "executing git command")
-		require.Contains(t, entries[1].Message, "command=git-receive-pack")
-		require.Contains(t, entries[1].Message, "remote_ip=127.0.0.1")
-		require.Contains(t, entries[1].Message, "gl_key_type=key")
-		require.Contains(t, entries[1].Message, "gl_key_id=123")
-		require.Contains(t, entries[1].Message, "correlation_id=a-correlation-id")
+		for k, v := range map[string]string{
+			"gitaly-feature-cache_invalidator":        "true",
+			"gitaly-feature-inforef_uploadpack_cache": "false",
+			"x-gitlab-client-name":                    "gitlab-shell-tests-git-receive-pack",
+			"key_id":                                  "123",
+			"user_id":                                 "1",
+			"remote_ip":                               "127.0.0.1",
+			"key_type":                                "key",
+		} {
+			actual := testServer.ReceivedMD[k]
+			require.Len(t, actual, 1)
+			require.Equal(t, v, actual[0])
+		}
+		require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+		require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 	}
 }
diff --git a/internal/command/uploadarchive/gitalycall_test.go b/internal/command/uploadarchive/gitalycall_test.go
--- a/internal/command/uploadarchive/gitalycall_test.go
+++ b/internal/command/uploadarchive/gitalycall_test.go
@@ -5,7 +5,6 @@
 	"context"
 	"testing"
 
-	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
@@ -13,12 +12,12 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
 )
 
-func TestUploadPack(t *testing.T) {
-	gitalyAddress, _ := testserver.StartGitalyServer(t)
+func TestUploadArchive(t *testing.T) {
+	gitalyAddress, testServer := testserver.StartGitalyServer(t)
 
 	requests := requesthandlers.BuildAllowedWithGitalyHandlers(t, gitalyAddress)
 	url := testserver.StartHttpServer(t, requests)
@@ -29,13 +28,25 @@
 	userId := "1"
 	repo := "group/repo"
 
+	env := sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: "git-upload-archive " + repo,
+		RemoteAddr:      "127.0.0.1",
+	}
+
+	args := &commandargs.Shell{
+		GitlabKeyId: userId,
+		CommandType: commandargs.UploadArchive,
+		SshArgs:     []string{"git-upload-archive", repo},
+		Env:         env,
+	}
+
 	cmd := &Command{
 		Config:     &config.Config{GitlabUrl: url},
-		Args:       &commandargs.Shell{GitlabKeyId: userId, CommandType: commandargs.UploadArchive, SshArgs: []string{"git-upload-archive", repo}},
+		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
 
-	hook := testhelper.SetupLogger()
 	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
@@ -44,13 +55,19 @@
 
 	require.Equal(t, "UploadArchive: "+repo, output.String())
 
-	require.True(t, testhelper.WaitForLogEvent(hook))
-	entries := hook.AllEntries()
-	require.Equal(t, 2, len(entries))
-	require.Equal(t, logrus.InfoLevel, entries[1].Level)
-	require.Contains(t, entries[1].Message, "executing git command")
-	require.Contains(t, entries[1].Message, "command=git-upload-archive")
-	require.Contains(t, entries[1].Message, "gl_key_type=key")
-	require.Contains(t, entries[1].Message, "gl_key_id=123")
-	require.Contains(t, entries[1].Message, "correlation_id=a-correlation-id")
+	for k, v := range map[string]string{
+		"gitaly-feature-cache_invalidator":        "true",
+		"gitaly-feature-inforef_uploadpack_cache": "false",
+		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-archive",
+		"key_id":                                  "123",
+		"user_id":                                 "1",
+		"remote_ip":                               "127.0.0.1",
+		"key_type":                                "key",
+	} {
+		actual := testServer.ReceivedMD[k]
+		require.Len(t, actual, 1)
+		require.Equal(t, v, actual[0])
+	}
+	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 }
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -4,7 +4,6 @@
 	"bytes"
 	"context"
 	"testing"
-	"time"
 
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
@@ -13,7 +12,7 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
 )
 
@@ -29,13 +28,25 @@
 	userId := "1"
 	repo := "group/repo"
 
+	env := sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: "git-upload-pack " + repo,
+		RemoteAddr:      "127.0.0.1",
+	}
+
+	args := &commandargs.Shell{
+		GitlabKeyId: userId,
+		CommandType: commandargs.UploadPack,
+		SshArgs:     []string{"git-upload-pack", repo},
+		Env:         env,
+	}
+
 	cmd := &Command{
 		Config:     &config.Config{GitlabUrl: url},
-		Args:       &commandargs.Shell{GitlabKeyId: userId, CommandType: commandargs.UploadPack, SshArgs: []string{"git-upload-pack", repo}},
+		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
 
-	hook := testhelper.SetupLogger()
 	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
@@ -43,25 +54,20 @@
 	require.NoError(t, err)
 
 	require.Equal(t, "UploadPack: "+repo, output.String())
-	require.Eventually(t, func() bool {
-		entries := hook.AllEntries()
-
-		require.Equal(t, 2, len(entries))
-		require.Contains(t, entries[1].Message, "executing git command")
-		require.Contains(t, entries[1].Message, "command=git-upload-pack")
-		require.Contains(t, entries[1].Message, "gl_key_type=key")
-		require.Contains(t, entries[1].Message, "gl_key_id=123")
-		require.Contains(t, entries[1].Message, "correlation_id=a-correlation-id")
-		return true
-	}, time.Second, time.Millisecond)
 
 	for k, v := range map[string]string{
 		"gitaly-feature-cache_invalidator":        "true",
 		"gitaly-feature-inforef_uploadpack_cache": "false",
+		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-pack",
+		"key_id":                                  "123",
+		"user_id":                                 "1",
+		"remote_ip":                               "127.0.0.1",
+		"key_type":                                "key",
 	} {
 		actual := testServer.ReceivedMD[k]
 		require.Len(t, actual, 1)
 		require.Equal(t, v, actual[0])
 	}
 	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 }
diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go
--- a/internal/testhelper/testhelper.go
+++ b/internal/testhelper/testhelper.go
@@ -7,11 +7,8 @@
 	"path"
 	"runtime"
 	"testing"
-	"time"
 
 	"github.com/otiai10/copy"
-	"github.com/sirupsen/logrus"
-	"github.com/sirupsen/logrus/hooks/test"
 	"github.com/stretchr/testify/require"
 )
 
@@ -75,25 +72,3 @@
 	err := os.Setenv(key, value)
 	return func() { os.Setenv(key, oldValue) }, err
 }
-
-func SetupLogger() *test.Hook {
-	logger, hook := test.NewNullLogger()
-	logrus.SetOutput(logger.Writer())
-
-	return hook
-}
-
-// logrus fires a Goroutine to write the output log, but there's no way to
-// flush all outstanding hooks to fire. We just wait up to a second
-// for an event to appear.
-func WaitForLogEvent(hook *test.Hook) bool {
-	for i := 0; i < 10; i++ {
-		if entry := hook.LastEntry(); entry != nil {
-			return true
-		}
-
-		time.Sleep(100 * time.Millisecond)
-	}
-
-	return false
-}
# HG changeset patch
# User Robert May <rmay@gitlab.com>
# Date 1624893480 -3600
#      Mon Jun 28 16:18:00 2021 +0100
# Node ID d7381c5162dcba14ef705a9d2d292b9073ebe6e3
# Parent  e217294e7c0ea89fe37160a49b0d3ced586063ca
Modify regex to prevent partial matches

diff --git a/internal/command/commandargs/command_args_test.go b/internal/command/commandargs/command_args_test.go
--- a/internal/command/commandargs/command_args_test.go
+++ b/internal/command/commandargs/command_args_test.go
@@ -23,14 +23,19 @@
 			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
 			arguments:    []string{},
 			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{}, CommandType: Discover, Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		},
-		{
+		}, {
 			desc:         "It finds the key id in any passed arguments",
 			executable:   &executable.Executable{Name: executable.GitlabShell},
 			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
 			arguments:    []string{"hello", "key-123"},
 			expectedArgs: &Shell{Arguments: []string{"hello", "key-123"}, SshArgs: []string{}, CommandType: Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
 		}, {
+			desc:         "It finds the key id only if the argument is of <key-id> format",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "username-key-123"},
+			expectedArgs: &Shell{Arguments: []string{"hello", "username-key-123"}, SshArgs: []string{}, CommandType: Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		}, {
 			desc:         "It finds the username in any passed arguments",
 			executable:   &executable.Executable{Name: executable.GitlabShell},
 			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
diff --git a/internal/command/commandargs/shell.go b/internal/command/commandargs/shell.go
--- a/internal/command/commandargs/shell.go
+++ b/internal/command/commandargs/shell.go
@@ -20,8 +20,8 @@
 )
 
 var (
-	whoKeyRegex      = regexp.MustCompile(`\bkey-(?P<keyid>\d+)\b`)
-	whoUsernameRegex = regexp.MustCompile(`\busername-(?P<username>\S+)\b`)
+	whoKeyRegex      = regexp.MustCompile(`\Akey-(?P<keyid>\d+)\z`)
+	whoUsernameRegex = regexp.MustCompile(`\Ausername-(?P<username>\S+)\z`)
 )
 
 type Shell struct {
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1628044275 0
#      Wed Aug 04 02:31:15 2021 +0000
# Node ID bf50f08857a03c5cd33abcd3e739c71d978306b8
# Parent  13bc78f1f276a64b306d4d26ea67239804c8b634
# Parent  d7381c5162dcba14ef705a9d2d292b9073ebe6e3
Merge branch 'security-300265' into 'main'

Modify regex to prevent partial matches

See merge request gitlab-org/security/gitlab-shell!6

diff --git a/internal/command/commandargs/command_args_test.go b/internal/command/commandargs/command_args_test.go
--- a/internal/command/commandargs/command_args_test.go
+++ b/internal/command/commandargs/command_args_test.go
@@ -23,14 +23,19 @@
 			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
 			arguments:    []string{},
 			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{}, CommandType: Discover, Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		},
-		{
+		}, {
 			desc:         "It finds the key id in any passed arguments",
 			executable:   &executable.Executable{Name: executable.GitlabShell},
 			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
 			arguments:    []string{"hello", "key-123"},
 			expectedArgs: &Shell{Arguments: []string{"hello", "key-123"}, SshArgs: []string{}, CommandType: Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
 		}, {
+			desc:         "It finds the key id only if the argument is of <key-id> format",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "username-key-123"},
+			expectedArgs: &Shell{Arguments: []string{"hello", "username-key-123"}, SshArgs: []string{}, CommandType: Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		}, {
 			desc:         "It finds the username in any passed arguments",
 			executable:   &executable.Executable{Name: executable.GitlabShell},
 			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
diff --git a/internal/command/commandargs/shell.go b/internal/command/commandargs/shell.go
--- a/internal/command/commandargs/shell.go
+++ b/internal/command/commandargs/shell.go
@@ -20,8 +20,8 @@
 )
 
 var (
-	whoKeyRegex      = regexp.MustCompile(`\bkey-(?P<keyid>\d+)\b`)
-	whoUsernameRegex = regexp.MustCompile(`\busername-(?P<username>\S+)\b`)
+	whoKeyRegex      = regexp.MustCompile(`\Akey-(?P<keyid>\d+)\z`)
+	whoUsernameRegex = regexp.MustCompile(`\Ausername-(?P<username>\S+)\z`)
 )
 
 type Shell struct {
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1627999633 -3600
#      Tue Aug 03 15:07:13 2021 +0100
# Node ID 3851cebeb009f19cf6792a5e48d8d898a71229c5
# Parent  13bc78f1f276a64b306d4d26ea67239804c8b634
Switch to labkit for logging system setup

- We start supporting the "color" format for logs.
- We now respond to SIGHUP by reopening the log file.
- We now respect the log format when no log filename is specified.

Output to syslog in the event of logging system setup is preserved in
OpenSSH mode.

Changelog: added

diff --git a/cmd/check/main.go b/cmd/check/main.go
--- a/cmd/check/main.go
+++ b/cmd/check/main.go
@@ -31,7 +31,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
 	if err != nil {
diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go
--- a/cmd/gitlab-shell-authorized-keys-check/main.go
+++ b/cmd/gitlab-shell-authorized-keys-check/main.go
@@ -32,7 +32,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
 	if err != nil {
diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go
--- a/cmd/gitlab-shell-authorized-principals-check/main.go
+++ b/cmd/gitlab-shell-authorized-principals-check/main.go
@@ -32,7 +32,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
 	if err != nil {
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -46,7 +46,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	env := sshenv.NewFromEnv()
 	cmd, err := command.New(executable, os.Args[1:], env, config, readWriter)
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -229,7 +229,7 @@
 	t.Cleanup(func() { pw.Close() })
 
 	scanner := bufio.NewScanner(pr)
-	extractor := regexp.MustCompile(`tcp_address="([0-9a-f\[\]\.:]+)"`)
+	extractor := regexp.MustCompile(`"tcp_address":"([0-9a-f\[\]\.:]+)"`)
 
 	ctx, cancel := context.WithCancel(context.Background())
 	cmd := exec.CommandContext(ctx, sshdPath, "-config-dir", dir)
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -63,7 +63,8 @@
 
 	cfg.ApplyGlobalState()
 
-	logger.ConfigureStandalone(cfg)
+	logCloser := logger.ConfigureStandalone(cfg)
+	defer logCloser.Close()
 
 	ctx, finished := command.Setup("gitlab-sshd", cfg)
 	defer finished()
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -10,10 +10,9 @@
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.0
 	github.com/prometheus/client_golang v1.10.0
-	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1
-	gitlab.com/gitlab-org/labkit v1.4.1
+	gitlab.com/gitlab-org/labkit v1.7.0
 	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/grpc v1.37.0
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -16,6 +16,7 @@
 cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
 cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
 cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
 cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
@@ -38,6 +39,8 @@
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -81,6 +84,8 @@
 github.com/avast/retry-go v2.4.2+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.37.0 h1:GzFnhOIsrGyQ69s7VgqtrG2BG8v7X7vwB3Xpbd/DBBk=
+github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -91,6 +96,8 @@
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 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=
 github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -214,6 +221,8 @@
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -274,6 +283,7 @@
 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210125172800-10e9aeb4a998/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo=
@@ -343,6 +353,10 @@
 github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
 github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@@ -645,8 +659,8 @@
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
-gitlab.com/gitlab-org/labkit v1.4.1 h1:mh4g+c/esQSWCVQJ197aftrIbXlhXCtiLC8RgSvyCx0=
-gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
+gitlab.com/gitlab-org/labkit v1.7.0 h1:ufG42cvJ+VkqaUWVEoat/h9PGcW9FFSUPQFz7uwL5wc=
+gitlab.com/gitlab-org/labkit v1.7.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
@@ -657,6 +671,7 @@
 go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -771,6 +786,7 @@
 golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
@@ -783,6 +799,7 @@
 golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
@@ -937,6 +954,7 @@
 golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -969,6 +987,7 @@
 google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
 google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.37.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
@@ -1020,6 +1039,8 @@
 google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -2,37 +2,54 @@
 
 import (
 	"fmt"
+	"io"
 	"io/ioutil"
 	"log/syslog"
 	"os"
+	"time"
 
-	log "github.com/sirupsen/logrus"
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
-type UTCFormatter struct {
-	log.Formatter
+func logFmt(inFmt string) string {
+	// Hide the "combined" format, since that makes no sense in gitlab-shell.
+	// The default is JSON when unspecified.
+	if inFmt == "" || inFmt == "combined" {
+		return "json"
+	}
+
+	return inFmt
 }
 
-func (u UTCFormatter) Format(e *log.Entry) ([]byte, error) {
-	e.Time = e.Time.UTC()
+func logFile(inFile string) string {
+	if inFile == "" {
+		return "stderr"
+	}
 
-	return u.Formatter.Format(e)
+	return inFile
 }
 
-func configureLogFormat(cfg *config.Config) {
-	if cfg.LogFormat == "json" {
-		log.SetFormatter(UTCFormatter{&log.JSONFormatter{}})
-	} else {
-		log.SetFormatter(UTCFormatter{&log.TextFormatter{}})
+func buildOpts(cfg *config.Config) []log.LoggerOption {
+	return []log.LoggerOption{
+		log.WithFormatter(logFmt(cfg.LogFormat)),
+		log.WithOutputName(logFile(cfg.LogFile)),
+		log.WithTimezone(time.UTC),
 	}
 }
 
 // Configure configures the logging singleton for operation inside a remote TTY (like SSH). In this
 // mode an empty LogFile is not accepted and syslog is used as a fallback when LogFile could not be
 // opened for writing.
-func Configure(cfg *config.Config) {
-	logFile, err := os.OpenFile(cfg.LogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
+func Configure(cfg *config.Config) io.Closer {
+	var closer io.Closer = ioutil.NopCloser(nil)
+	err := fmt.Errorf("No logfile specified")
+
+	if cfg.LogFile != "" {
+		closer, err = log.Initialize(buildOpts(cfg)...)
+	}
+
 	if err != nil {
 		progName, _ := os.Executable()
 		syslogLogger, syslogLoggerErr := syslog.NewLogger(syslog.LOG_ERR|syslog.LOG_USER, 0)
@@ -44,29 +61,35 @@
 			fmt.Fprintf(os.Stderr, msg)
 		}
 
-		// Discard logs since a log file was specified but couldn't be opened
-		log.SetOutput(ioutil.Discard)
+		cfg.LogFile = "/dev/null"
+		closer, err = log.Initialize(buildOpts(cfg)...)
+		if err != nil {
+			log.WithError(err).Warn("Unable to configure logging to /dev/null, leaving unconfigured")
+		}
 	}
 
-	log.SetOutput(logFile)
-
-	configureLogFormat(cfg)
+	return closer
 }
 
 // ConfigureStandalone configures the logging singleton for standalone operation. In this mode an
-// empty LogFile is treated as logging to standard output and standard output is used as a fallback
+// empty LogFile is treated as logging to stderr, and standard output is used as a fallback
 // when LogFile could not be opened for writing.
-func ConfigureStandalone(cfg *config.Config) {
-	if cfg.LogFile == "" {
-		return
+func ConfigureStandalone(cfg *config.Config) io.Closer {
+	closer, err1 := log.Initialize(buildOpts(cfg)...)
+	if err1 != nil {
+		var err2 error
+
+		cfg.LogFile = "stdout"
+		closer, err2 = log.Initialize(buildOpts(cfg)...)
+
+		// Output this after the logger has been configured!
+		log.WithError(err1).WithField("log_file", cfg.LogFile).Warn("Unable to configure logging, falling back to STDOUT")
+
+		// LabKit v1.7.0 doesn't have any conditions where logging to "stdout" will fail
+		if err2 != nil {
+			log.WithError(err2).Warn("Unable to configure logging to STDOUT, leaving unconfigured")
+		}
 	}
 
-	logFile, err := os.OpenFile(cfg.LogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
-	if err != nil {
-		log.Printf("Unable to configure logging, falling back to stdout: %v", err)
-		return
-	}
-	log.SetOutput(logFile)
-
-	configureLogFormat(cfg)
+	return closer
 }
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -7,8 +7,9 @@
 	"strings"
 	"testing"
 
-	log "github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
@@ -22,7 +23,9 @@
 		LogFormat: "json",
 	}
 
-	Configure(&config)
+	closer := Configure(&config)
+	defer closer.Close()
+
 	log.Info("this is a test")
 
 	tmpFile.Close()
@@ -42,7 +45,9 @@
 		LogFormat: "json",
 	}
 
-	Configure(&config)
+	closer := Configure(&config)
+	defer closer.Close()
+
 	log.Info("this is a test")
 }
 
@@ -57,7 +62,9 @@
 		LogFormat: "json",
 	}
 
-	Configure(&config)
+	closer := Configure(&config)
+	defer closer.Close()
+
 	log.Info("this is a test")
 
 	data, err := ioutil.ReadFile(tmpFile.Name())
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1628066623 0
#      Wed Aug 04 08:43:43 2021 +0000
# Node ID 6ea629933069190232b9adb008f60e84d889904f
# Parent  bf50f08857a03c5cd33abcd3e739c71d978306b8
# Parent  3851cebeb009f19cf6792a5e48d8d898a71229c5
Merge branch '499-use-labkit-logging-initialize' into 'main'

Switch to labkit for logging system setup

Closes #271

See merge request gitlab-org/gitlab-shell!504

diff --git a/cmd/check/main.go b/cmd/check/main.go
--- a/cmd/check/main.go
+++ b/cmd/check/main.go
@@ -31,7 +31,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
 	if err != nil {
diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go
--- a/cmd/gitlab-shell-authorized-keys-check/main.go
+++ b/cmd/gitlab-shell-authorized-keys-check/main.go
@@ -32,7 +32,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
 	if err != nil {
diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go
--- a/cmd/gitlab-shell-authorized-principals-check/main.go
+++ b/cmd/gitlab-shell-authorized-principals-check/main.go
@@ -32,7 +32,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
 	if err != nil {
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -46,7 +46,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	env := sshenv.NewFromEnv()
 	cmd, err := command.New(executable, os.Args[1:], env, config, readWriter)
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -229,7 +229,7 @@
 	t.Cleanup(func() { pw.Close() })
 
 	scanner := bufio.NewScanner(pr)
-	extractor := regexp.MustCompile(`tcp_address="([0-9a-f\[\]\.:]+)"`)
+	extractor := regexp.MustCompile(`"tcp_address":"([0-9a-f\[\]\.:]+)"`)
 
 	ctx, cancel := context.WithCancel(context.Background())
 	cmd := exec.CommandContext(ctx, sshdPath, "-config-dir", dir)
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -63,7 +63,8 @@
 
 	cfg.ApplyGlobalState()
 
-	logger.ConfigureStandalone(cfg)
+	logCloser := logger.ConfigureStandalone(cfg)
+	defer logCloser.Close()
 
 	ctx, finished := command.Setup("gitlab-sshd", cfg)
 	defer finished()
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -10,10 +10,9 @@
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.0
 	github.com/prometheus/client_golang v1.10.0
-	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1
-	gitlab.com/gitlab-org/labkit v1.4.1
+	gitlab.com/gitlab-org/labkit v1.7.0
 	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/grpc v1.37.0
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -16,6 +16,7 @@
 cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
 cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
 cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
 cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
@@ -38,6 +39,8 @@
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -81,6 +84,8 @@
 github.com/avast/retry-go v2.4.2+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.37.0 h1:GzFnhOIsrGyQ69s7VgqtrG2BG8v7X7vwB3Xpbd/DBBk=
+github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -91,6 +96,8 @@
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 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=
 github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -214,6 +221,8 @@
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -274,6 +283,7 @@
 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210125172800-10e9aeb4a998/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo=
@@ -343,6 +353,10 @@
 github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
 github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@@ -645,8 +659,8 @@
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
-gitlab.com/gitlab-org/labkit v1.4.1 h1:mh4g+c/esQSWCVQJ197aftrIbXlhXCtiLC8RgSvyCx0=
-gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
+gitlab.com/gitlab-org/labkit v1.7.0 h1:ufG42cvJ+VkqaUWVEoat/h9PGcW9FFSUPQFz7uwL5wc=
+gitlab.com/gitlab-org/labkit v1.7.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
@@ -657,6 +671,7 @@
 go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -771,6 +786,7 @@
 golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
@@ -783,6 +799,7 @@
 golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
@@ -937,6 +954,7 @@
 golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -969,6 +987,7 @@
 google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
 google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.37.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
@@ -1020,6 +1039,8 @@
 google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -2,37 +2,54 @@
 
 import (
 	"fmt"
+	"io"
 	"io/ioutil"
 	"log/syslog"
 	"os"
+	"time"
 
-	log "github.com/sirupsen/logrus"
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
-type UTCFormatter struct {
-	log.Formatter
+func logFmt(inFmt string) string {
+	// Hide the "combined" format, since that makes no sense in gitlab-shell.
+	// The default is JSON when unspecified.
+	if inFmt == "" || inFmt == "combined" {
+		return "json"
+	}
+
+	return inFmt
 }
 
-func (u UTCFormatter) Format(e *log.Entry) ([]byte, error) {
-	e.Time = e.Time.UTC()
+func logFile(inFile string) string {
+	if inFile == "" {
+		return "stderr"
+	}
 
-	return u.Formatter.Format(e)
+	return inFile
 }
 
-func configureLogFormat(cfg *config.Config) {
-	if cfg.LogFormat == "json" {
-		log.SetFormatter(UTCFormatter{&log.JSONFormatter{}})
-	} else {
-		log.SetFormatter(UTCFormatter{&log.TextFormatter{}})
+func buildOpts(cfg *config.Config) []log.LoggerOption {
+	return []log.LoggerOption{
+		log.WithFormatter(logFmt(cfg.LogFormat)),
+		log.WithOutputName(logFile(cfg.LogFile)),
+		log.WithTimezone(time.UTC),
 	}
 }
 
 // Configure configures the logging singleton for operation inside a remote TTY (like SSH). In this
 // mode an empty LogFile is not accepted and syslog is used as a fallback when LogFile could not be
 // opened for writing.
-func Configure(cfg *config.Config) {
-	logFile, err := os.OpenFile(cfg.LogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
+func Configure(cfg *config.Config) io.Closer {
+	var closer io.Closer = ioutil.NopCloser(nil)
+	err := fmt.Errorf("No logfile specified")
+
+	if cfg.LogFile != "" {
+		closer, err = log.Initialize(buildOpts(cfg)...)
+	}
+
 	if err != nil {
 		progName, _ := os.Executable()
 		syslogLogger, syslogLoggerErr := syslog.NewLogger(syslog.LOG_ERR|syslog.LOG_USER, 0)
@@ -44,29 +61,35 @@
 			fmt.Fprintf(os.Stderr, msg)
 		}
 
-		// Discard logs since a log file was specified but couldn't be opened
-		log.SetOutput(ioutil.Discard)
+		cfg.LogFile = "/dev/null"
+		closer, err = log.Initialize(buildOpts(cfg)...)
+		if err != nil {
+			log.WithError(err).Warn("Unable to configure logging to /dev/null, leaving unconfigured")
+		}
 	}
 
-	log.SetOutput(logFile)
-
-	configureLogFormat(cfg)
+	return closer
 }
 
 // ConfigureStandalone configures the logging singleton for standalone operation. In this mode an
-// empty LogFile is treated as logging to standard output and standard output is used as a fallback
+// empty LogFile is treated as logging to stderr, and standard output is used as a fallback
 // when LogFile could not be opened for writing.
-func ConfigureStandalone(cfg *config.Config) {
-	if cfg.LogFile == "" {
-		return
+func ConfigureStandalone(cfg *config.Config) io.Closer {
+	closer, err1 := log.Initialize(buildOpts(cfg)...)
+	if err1 != nil {
+		var err2 error
+
+		cfg.LogFile = "stdout"
+		closer, err2 = log.Initialize(buildOpts(cfg)...)
+
+		// Output this after the logger has been configured!
+		log.WithError(err1).WithField("log_file", cfg.LogFile).Warn("Unable to configure logging, falling back to STDOUT")
+
+		// LabKit v1.7.0 doesn't have any conditions where logging to "stdout" will fail
+		if err2 != nil {
+			log.WithError(err2).Warn("Unable to configure logging to STDOUT, leaving unconfigured")
+		}
 	}
 
-	logFile, err := os.OpenFile(cfg.LogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
-	if err != nil {
-		log.Printf("Unable to configure logging, falling back to stdout: %v", err)
-		return
-	}
-	log.SetOutput(logFile)
-
-	configureLogFormat(cfg)
+	return closer
 }
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -7,8 +7,9 @@
 	"strings"
 	"testing"
 
-	log "github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
@@ -22,7 +23,9 @@
 		LogFormat: "json",
 	}
 
-	Configure(&config)
+	closer := Configure(&config)
+	defer closer.Close()
+
 	log.Info("this is a test")
 
 	tmpFile.Close()
@@ -42,7 +45,9 @@
 		LogFormat: "json",
 	}
 
-	Configure(&config)
+	closer := Configure(&config)
+	defer closer.Close()
+
 	log.Info("this is a test")
 }
 
@@ -57,7 +62,9 @@
 		LogFormat: "json",
 	}
 
-	Configure(&config)
+	closer := Configure(&config)
+	defer closer.Close()
+
 	log.Info("this is a test")
 
 	data, err := ioutil.ReadFile(tmpFile.Name())
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1628701200 0
#      Wed Aug 11 17:00:00 2021 +0000
# Node ID 8dbc9ecf1d7b9d31c961acf9df5060f9ef71ffd3
# Parent  6ea629933069190232b9adb008f60e84d889904f
build: bump go to 1.15

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module gitlab.com/gitlab-org/gitlab-shell
 
-go 1.13
+go 1.15
 
 require (
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -219,7 +219,6 @@
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1628701283 0
#      Wed Aug 11 17:01:23 2021 +0000
# Node ID 4d1c9a805e96f0cf67572ce3b9aa97cfaf5854ed
# Parent  8dbc9ecf1d7b9d31c961acf9df5060f9ef71ffd3
test: remove go 1.14 test job

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -52,13 +52,6 @@
   script:
     - make verify test
 
-go:1.14:
-  extends: .test
-  image: golang:1.14
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
 go:1.15:
   extends: .test
   image: golang:1.15
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1628737339 0
#      Thu Aug 12 03:02:19 2021 +0000
# Node ID e60f605d38985002cfe0bc98354e6ada8c4c5013
# Parent  6ea629933069190232b9adb008f60e84d889904f
# Parent  4d1c9a805e96f0cf67572ce3b9aa97cfaf5854ed
Merge branch 'bump/go1.15' into 'main'

build: bump go to 1.15

See merge request gitlab-org/gitlab-shell!505

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -52,13 +52,6 @@
   script:
     - make verify test
 
-go:1.14:
-  extends: .test
-  image: golang:1.14
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
 go:1.15:
   extends: .test
   image: golang:1.15
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module gitlab.com/gitlab-org/gitlab-shell
 
-go 1.13
+go 1.15
 
 require (
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -219,7 +219,6 @@
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1628703619 0
#      Wed Aug 11 17:40:19 2021 +0000
# Node ID 8500146e19b08043dd21f4bbc8173d3f1fe50bc6
# Parent  6ea629933069190232b9adb008f60e84d889904f
refactor: update usage of NewHTTPClient to NewHTTPClientWithOpts

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -59,7 +59,8 @@
 
 			secret := "sssh, it's a secret"
 
-			httpClient := NewHTTPClient(url, tc.relativeURLRoot, tc.caFile, "", false, 1)
+			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", false, 1, nil)
+			require.NoError(t, err)
 
 			client, err := NewGitlabNetClient("", "", secret, httpClient)
 			require.NoError(t, err)
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -17,7 +17,8 @@
 func TestReadTimeout(t *testing.T) {
 	expectedSeconds := uint64(300)
 
-	client := NewHTTPClient("http://localhost:3000", "", "", "", false, expectedSeconds)
+	client, err := NewHTTPClientWithOpts("http://localhost:3000", "", "", "", false, expectedSeconds, nil)
+	require.NoError(t, err)
 
 	require.NotNil(t, client)
 	require.Equal(t, time.Duration(expectedSeconds)*time.Second, client.Client.Timeout)
@@ -122,7 +123,8 @@
 func setup(t *testing.T, username, password string, requests []testserver.TestRequestHandler) *GitlabNetClient {
 	url := testserver.StartHttpServer(t, requests)
 
-	httpClient := NewHTTPClient(url, "", "", "", false, 1)
+	httpClient, err := NewHTTPClientWithOpts(url, "", "", "", false, 1, nil)
+	require.NoError(t, err)
 
 	client, err := NewGitlabNetClient(username, password, "", httpClient)
 	require.NoError(t, err)
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -15,6 +15,7 @@
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
@@ -97,14 +98,18 @@
 
 func (c *Config) HttpClient() *client.HttpClient {
 	c.httpClientOnce.Do(func() {
-		client := client.NewHTTPClient(
+		client, err := client.NewHTTPClientWithOpts(
 			c.GitlabUrl,
 			c.GitlabRelativeURLRoot,
 			c.HttpSettings.CaFile,
 			c.HttpSettings.CaPath,
 			c.HttpSettings.SelfSignedCert,
 			c.HttpSettings.ReadTimeoutSeconds,
+			nil,
 		)
+		if err != nil {
+			log.WithError(err).Fatal("new http client with opts")
+		}
 
 		tr := client.Transport
 		client.Transport = promhttp.InstrumentRoundTripperDuration(metrics.HttpRequestDuration, tr)
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1628862681 0
#      Fri Aug 13 13:51:21 2021 +0000
# Node ID 18156564c912d06693c21ccc74b01866a7ea862d
# Parent  8500146e19b08043dd21f4bbc8173d3f1fe50bc6
refactor: change httpclient to return an error

diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -15,7 +15,6 @@
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
-	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
@@ -59,6 +58,7 @@
 	Server         ServerConfig       `yaml:"sshd"`
 
 	httpClient     *client.HttpClient
+	httpClientErr  error
 	httpClientOnce sync.Once
 }
 
@@ -96,7 +96,7 @@
 	}
 }
 
-func (c *Config) HttpClient() *client.HttpClient {
+func (c *Config) HttpClient() (*client.HttpClient, error) {
 	c.httpClientOnce.Do(func() {
 		client, err := client.NewHTTPClientWithOpts(
 			c.GitlabUrl,
@@ -108,7 +108,8 @@
 			nil,
 		)
 		if err != nil {
-			log.WithError(err).Fatal("new http client with opts")
+			c.httpClientErr = err
+			return
 		}
 
 		tr := client.Transport
@@ -117,7 +118,7 @@
 		c.httpClient = client
 	})
 
-	return c.httpClient
+	return c.httpClient, c.httpClientErr
 }
 
 // NewFromDirExternal returns a new config from a given root dir. It also applies defaults appropriate for
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -29,9 +29,10 @@
 	url := testserver.StartHttpServer(t, []testserver.TestRequestHandler{})
 
 	config := &Config{GitlabUrl: url}
-	client := config.HttpClient()
+	client, err := config.HttpClient()
+	require.NoError(t, err)
 
-	_, err := client.Get("http://host.com/path")
+	_, err = client.Get("http://host.com/path")
 	require.NoError(t, err)
 
 	ms, err := prometheus.DefaultGatherer.Gather()
diff --git a/internal/gitlabnet/client.go b/internal/gitlabnet/client.go
--- a/internal/gitlabnet/client.go
+++ b/internal/gitlabnet/client.go
@@ -15,7 +15,10 @@
 )
 
 func GetClient(config *config.Config) (*client.GitlabNetClient, error) {
-	httpClient := config.HttpClient()
+	httpClient, err := config.HttpClient()
+	if err != nil {
+		return nil, err
+	}
 
 	if httpClient == nil {
 		return nil, fmt.Errorf("Unsupported protocol")
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1628866202 0
#      Fri Aug 13 14:50:02 2021 +0000
# Node ID edce299c5e1051efa54e516561c6ea0b59ea09e5
# Parent  e60f605d38985002cfe0bc98354e6ada8c4c5013
# Parent  18156564c912d06693c21ccc74b01866a7ea862d
Merge branch 'update/newclientopts' into 'main'

refactor: update usage of NewHTTPClient to NewHTTPClientWithOpts

Closes #484

See merge request gitlab-org/gitlab-shell!507

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -59,7 +59,8 @@
 
 			secret := "sssh, it's a secret"
 
-			httpClient := NewHTTPClient(url, tc.relativeURLRoot, tc.caFile, "", false, 1)
+			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", false, 1, nil)
+			require.NoError(t, err)
 
 			client, err := NewGitlabNetClient("", "", secret, httpClient)
 			require.NoError(t, err)
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -17,7 +17,8 @@
 func TestReadTimeout(t *testing.T) {
 	expectedSeconds := uint64(300)
 
-	client := NewHTTPClient("http://localhost:3000", "", "", "", false, expectedSeconds)
+	client, err := NewHTTPClientWithOpts("http://localhost:3000", "", "", "", false, expectedSeconds, nil)
+	require.NoError(t, err)
 
 	require.NotNil(t, client)
 	require.Equal(t, time.Duration(expectedSeconds)*time.Second, client.Client.Timeout)
@@ -122,7 +123,8 @@
 func setup(t *testing.T, username, password string, requests []testserver.TestRequestHandler) *GitlabNetClient {
 	url := testserver.StartHttpServer(t, requests)
 
-	httpClient := NewHTTPClient(url, "", "", "", false, 1)
+	httpClient, err := NewHTTPClientWithOpts(url, "", "", "", false, 1, nil)
+	require.NoError(t, err)
 
 	client, err := NewGitlabNetClient(username, password, "", httpClient)
 	require.NoError(t, err)
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -58,6 +58,7 @@
 	Server         ServerConfig       `yaml:"sshd"`
 
 	httpClient     *client.HttpClient
+	httpClientErr  error
 	httpClientOnce sync.Once
 }
 
@@ -95,16 +96,21 @@
 	}
 }
 
-func (c *Config) HttpClient() *client.HttpClient {
+func (c *Config) HttpClient() (*client.HttpClient, error) {
 	c.httpClientOnce.Do(func() {
-		client := client.NewHTTPClient(
+		client, err := client.NewHTTPClientWithOpts(
 			c.GitlabUrl,
 			c.GitlabRelativeURLRoot,
 			c.HttpSettings.CaFile,
 			c.HttpSettings.CaPath,
 			c.HttpSettings.SelfSignedCert,
 			c.HttpSettings.ReadTimeoutSeconds,
+			nil,
 		)
+		if err != nil {
+			c.httpClientErr = err
+			return
+		}
 
 		tr := client.Transport
 		client.Transport = promhttp.InstrumentRoundTripperDuration(metrics.HttpRequestDuration, tr)
@@ -112,7 +118,7 @@
 		c.httpClient = client
 	})
 
-	return c.httpClient
+	return c.httpClient, c.httpClientErr
 }
 
 // NewFromDirExternal returns a new config from a given root dir. It also applies defaults appropriate for
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -29,9 +29,10 @@
 	url := testserver.StartHttpServer(t, []testserver.TestRequestHandler{})
 
 	config := &Config{GitlabUrl: url}
-	client := config.HttpClient()
+	client, err := config.HttpClient()
+	require.NoError(t, err)
 
-	_, err := client.Get("http://host.com/path")
+	_, err = client.Get("http://host.com/path")
 	require.NoError(t, err)
 
 	ms, err := prometheus.DefaultGatherer.Gather()
diff --git a/internal/gitlabnet/client.go b/internal/gitlabnet/client.go
--- a/internal/gitlabnet/client.go
+++ b/internal/gitlabnet/client.go
@@ -15,7 +15,10 @@
 )
 
 func GetClient(config *config.Config) (*client.GitlabNetClient, error) {
-	httpClient := config.HttpClient()
+	httpClient, err := config.HttpClient()
+	if err != nil {
+		return nil, err
+	}
 
 	if httpClient == nil {
 		return nil, fmt.Errorf("Unsupported protocol")
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1628708524 0
#      Wed Aug 11 19:02:04 2021 +0000
# Node ID 2b4161c1cc984d75c75714c1c9e4ea08d5574731
# Parent  6ea629933069190232b9adb008f60e84d889904f
fix: validate client cert paths exist on disk before proceeding

diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -5,9 +5,11 @@
 	"crypto/tls"
 	"crypto/x509"
 	"errors"
+	"fmt"
 	"io/ioutil"
 	"net"
 	"net/http"
+	"os"
 	"path/filepath"
 	"strings"
 	"time"
@@ -25,6 +27,10 @@
 	defaultReadTimeoutSeconds = 300
 )
 
+var (
+	ErrCafileNotFound = errors.New("cafile not found")
+)
+
 type HttpClient struct {
 	*http.Client
 	Host string
@@ -60,15 +66,6 @@
 
 // NewHTTPClientWithOpts builds an HTTP client using the provided options
 func NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64, opts []HTTPClientOpt) (*HttpClient, error) {
-	hcc := &httpClientCfg{
-		caFile: caFile,
-		caPath: caPath,
-	}
-
-	for _, opt := range opts {
-		opt(hcc)
-	}
-
 	var transport *http.Transport
 	var host string
 	var err error
@@ -77,6 +74,19 @@
 	} else if strings.HasPrefix(gitlabURL, httpProtocol) {
 		transport, host = buildHttpTransport(gitlabURL)
 	} else if strings.HasPrefix(gitlabURL, httpsProtocol) {
+		hcc := &httpClientCfg{
+			caFile: caFile,
+			caPath: caPath,
+		}
+
+		for _, opt := range opts {
+			opt(hcc)
+		}
+
+		if _, err := os.Stat(caFile); err != nil {
+			return nil, fmt.Errorf("cannot find cafile '%s': %w", caFile, ErrCafileNotFound)
+		}
+
 		transport, host, err = buildHttpsTransport(*hcc, selfSignedCert, gitlabURL)
 		if err != nil {
 			return nil, err
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -28,10 +28,7 @@
 		{
 			desc:   "Valid CaPath",
 			caPath: path.Join(testhelper.TestRoot, "certs/valid"),
-		},
-		{
-			desc:       "Self signed cert option enabled",
-			selfSigned: true,
+			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
 		},
 		{
 			desc:       "Invalid cert with self signed cert option enabled",
@@ -51,7 +48,8 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath, tc.selfSigned)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath, tc.selfSigned)
+			require.NoError(t, err)
 
 			response, err := client.Get(context.Background(), "/hello")
 			require.NoError(t, err)
@@ -68,13 +66,15 @@
 
 func TestFailedRequests(t *testing.T) {
 	testCases := []struct {
-		desc   string
-		caFile string
-		caPath string
+		desc          string
+		caFile        string
+		caPath        string
+		expectedError string
 	}{
 		{
-			desc:   "Invalid CaFile",
-			caFile: path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+			desc:          "Invalid CaFile",
+			caFile:        path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+			expectedError: "Internal API unreachable",
 		},
 		{
 			desc:   "Invalid CaPath",
@@ -87,17 +87,21 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "", false)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "", false)
+			if tc.caFile == "" {
+				require.Error(t, err)
+				require.ErrorIs(t, err, ErrCafileNotFound)
+			} else {
+				_, err = client.Get(context.Background(), "/hello")
+				require.Error(t, err)
 
-			_, err := client.Get(context.Background(), "/hello")
-			require.Error(t, err)
-
-			require.Equal(t, err.Error(), "Internal API unreachable")
+				require.Equal(t, err.Error(), tc.expectedError)
+			}
 		})
 	}
 }
 
-func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string, selfSigned bool) *GitlabNetClient {
+func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string, selfSigned bool) (*GitlabNetClient, error) {
 	testhelper.PrepareTestRootDir(t)
 
 	requests := []testserver.TestRequestHandler{
@@ -119,10 +123,11 @@
 	}
 
 	httpClient, err := NewHTTPClientWithOpts(url, "", caFile, caPath, selfSigned, 1, opts)
-	require.NoError(t, err)
+	if err != nil {
+		return nil, err
+	}
 
 	client, err := NewGitlabNetClient("", "", "", httpClient)
-	require.NoError(t, err)
 
-	return client
+	return client, err
 }
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1628708688 0
#      Wed Aug 11 19:04:48 2021 +0000
# Node ID 3a36fa325bb40b48473f63e9a753efdc1c984ac0
# Parent  2b4161c1cc984d75c75714c1c9e4ea08d5574731
fix: make sure ErrCafileNotFound is returned only when the file doesn't exist

diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -84,7 +84,10 @@
 		}
 
 		if _, err := os.Stat(caFile); err != nil {
-			return nil, fmt.Errorf("cannot find cafile '%s': %w", caFile, ErrCafileNotFound)
+			if os.IsNotExist(err) {
+				return nil, fmt.Errorf("cannot find cafile '%s': %w", caFile, ErrCafileNotFound)
+			}
+			return nil, err
 		}
 
 		transport, host, err = buildHttpsTransport(*hcc, selfSignedCert, gitlabURL)
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1629221837 0
#      Tue Aug 17 17:37:17 2021 +0000
# Node ID 8fcbb1e258a9106764517736fc9582f52a6d80c7
# Parent  3a36fa325bb40b48473f63e9a753efdc1c984ac0
test: move os.stat check before the hcc creation

diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -74,6 +74,13 @@
 	} else if strings.HasPrefix(gitlabURL, httpProtocol) {
 		transport, host = buildHttpTransport(gitlabURL)
 	} else if strings.HasPrefix(gitlabURL, httpsProtocol) {
+		if _, err := os.Stat(caFile); err != nil {
+			if os.IsNotExist(err) {
+				return nil, fmt.Errorf("cannot find cafile '%s': %w", caFile, ErrCafileNotFound)
+			}
+			return nil, err
+		}
+
 		hcc := &httpClientCfg{
 			caFile: caFile,
 			caPath: caPath,
@@ -83,13 +90,6 @@
 			opt(hcc)
 		}
 
-		if _, err := os.Stat(caFile); err != nil {
-			if os.IsNotExist(err) {
-				return nil, fmt.Errorf("cannot find cafile '%s': %w", caFile, ErrCafileNotFound)
-			}
-			return nil, err
-		}
-
 		transport, host, err = buildHttpsTransport(*hcc, selfSignedCert, gitlabURL)
 		if err != nil {
 			return nil, err
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1629250631 0
#      Wed Aug 18 01:37:11 2021 +0000
# Node ID 8053a4a7b429fd4ce407f623882b2021729518d1
# Parent  edce299c5e1051efa54e516561c6ea0b59ea09e5
# Parent  8fcbb1e258a9106764517736fc9582f52a6d80c7
Merge branch 'verify/cafile' into 'main'

fix: validate client cert paths exist on disk before proceeding

Closes #486

See merge request gitlab-org/gitlab-shell!508

diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -5,9 +5,11 @@
 	"crypto/tls"
 	"crypto/x509"
 	"errors"
+	"fmt"
 	"io/ioutil"
 	"net"
 	"net/http"
+	"os"
 	"path/filepath"
 	"strings"
 	"time"
@@ -25,6 +27,10 @@
 	defaultReadTimeoutSeconds = 300
 )
 
+var (
+	ErrCafileNotFound = errors.New("cafile not found")
+)
+
 type HttpClient struct {
 	*http.Client
 	Host string
@@ -60,15 +66,6 @@
 
 // NewHTTPClientWithOpts builds an HTTP client using the provided options
 func NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64, opts []HTTPClientOpt) (*HttpClient, error) {
-	hcc := &httpClientCfg{
-		caFile: caFile,
-		caPath: caPath,
-	}
-
-	for _, opt := range opts {
-		opt(hcc)
-	}
-
 	var transport *http.Transport
 	var host string
 	var err error
@@ -77,6 +74,22 @@
 	} else if strings.HasPrefix(gitlabURL, httpProtocol) {
 		transport, host = buildHttpTransport(gitlabURL)
 	} else if strings.HasPrefix(gitlabURL, httpsProtocol) {
+		if _, err := os.Stat(caFile); err != nil {
+			if os.IsNotExist(err) {
+				return nil, fmt.Errorf("cannot find cafile '%s': %w", caFile, ErrCafileNotFound)
+			}
+			return nil, err
+		}
+
+		hcc := &httpClientCfg{
+			caFile: caFile,
+			caPath: caPath,
+		}
+
+		for _, opt := range opts {
+			opt(hcc)
+		}
+
 		transport, host, err = buildHttpsTransport(*hcc, selfSignedCert, gitlabURL)
 		if err != nil {
 			return nil, err
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -28,10 +28,7 @@
 		{
 			desc:   "Valid CaPath",
 			caPath: path.Join(testhelper.TestRoot, "certs/valid"),
-		},
-		{
-			desc:       "Self signed cert option enabled",
-			selfSigned: true,
+			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
 		},
 		{
 			desc:       "Invalid cert with self signed cert option enabled",
@@ -51,7 +48,8 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath, tc.selfSigned)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath, tc.selfSigned)
+			require.NoError(t, err)
 
 			response, err := client.Get(context.Background(), "/hello")
 			require.NoError(t, err)
@@ -68,13 +66,15 @@
 
 func TestFailedRequests(t *testing.T) {
 	testCases := []struct {
-		desc   string
-		caFile string
-		caPath string
+		desc          string
+		caFile        string
+		caPath        string
+		expectedError string
 	}{
 		{
-			desc:   "Invalid CaFile",
-			caFile: path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+			desc:          "Invalid CaFile",
+			caFile:        path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+			expectedError: "Internal API unreachable",
 		},
 		{
 			desc:   "Invalid CaPath",
@@ -87,17 +87,21 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "", false)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "", false)
+			if tc.caFile == "" {
+				require.Error(t, err)
+				require.ErrorIs(t, err, ErrCafileNotFound)
+			} else {
+				_, err = client.Get(context.Background(), "/hello")
+				require.Error(t, err)
 
-			_, err := client.Get(context.Background(), "/hello")
-			require.Error(t, err)
-
-			require.Equal(t, err.Error(), "Internal API unreachable")
+				require.Equal(t, err.Error(), tc.expectedError)
+			}
 		})
 	}
 }
 
-func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string, selfSigned bool) *GitlabNetClient {
+func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string, selfSigned bool) (*GitlabNetClient, error) {
 	testhelper.PrepareTestRootDir(t)
 
 	requests := []testserver.TestRequestHandler{
@@ -119,10 +123,11 @@
 	}
 
 	httpClient, err := NewHTTPClientWithOpts(url, "", caFile, caPath, selfSigned, 1, opts)
-	require.NoError(t, err)
+	if err != nil {
+		return nil, err
+	}
 
 	client, err := NewGitlabNetClient("", "", "", httpClient)
-	require.NoError(t, err)
 
-	return client
+	return client, err
 }
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1629337890 0
#      Thu Aug 19 01:51:30 2021 +0000
# Node ID 79145e25a794bb927b527cdc467734c9c5072c6e
# Parent  8053a4a7b429fd4ce407f623882b2021729518d1
ci: start integrating go 1.17 into the CI pipelines

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -66,9 +66,16 @@
     - make coverage
   coverage: '/\d+.\d+%/'
 
+go:1.17:
+  extends: .test
+  image: golang:1.17
+  after_script:
+    - make coverage
+  coverage: '/\d+.\d+%/'
+
 race:
   extends: .test
-  image: golang:1.16
+  image: golang:1.17
   script:
     - make test_golang_race
 
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1629341362 0
#      Thu Aug 19 02:49:22 2021 +0000
# Node ID c44eb8f53c8a45ecaf0f6a750ad793f56c248926
# Parent  8053a4a7b429fd4ce407f623882b2021729518d1
# Parent  79145e25a794bb927b527cdc467734c9c5072c6e
Merge branch 'test/go-1.17' into 'main'

ci: start integrating go 1.17 into the CI pipelines

See merge request gitlab-org/gitlab-shell!510

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -66,9 +66,16 @@
     - make coverage
   coverage: '/\d+.\d+%/'
 
+go:1.17:
+  extends: .test
+  image: golang:1.17
+  after_script:
+    - make coverage
+  coverage: '/\d+.\d+%/'
+
 race:
   extends: .test
-  image: golang:1.16
+  image: golang:1.17
   script:
     - make test_golang_race
 
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1629337974 0
#      Thu Aug 19 01:52:54 2021 +0000
# Node ID 7164a9b6bb008b61362352124e85969d76d8d405
# Parent  8053a4a7b429fd4ce407f623882b2021729518d1
build: bump go version to 1.16

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module gitlab.com/gitlab-org/gitlab-shell
 
-go 1.15
+go 1.16
 
 require (
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -162,7 +162,6 @@
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
 github.com/getsentry/raven-go v0.1.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
@@ -180,7 +179,6 @@
 github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
 github.com/git-lfs/wildmatch v1.0.4/go.mod h1:SdHAGnApDpnFYQ0vAxbniWR0sn7yLJ3QXo9RRfhn2ew=
 github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
-github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -268,10 +266,8 @@
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -498,7 +494,6 @@
 github.com/otiai10/copy v1.4.2 h1:RTiz2sol3eoXPLF4o+YWqEybwfUa/Q2Nkc4ZIUs3fwI=
 github.com/otiai10/copy v1.4.2/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E=
 github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
-github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI=
 github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
 github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw=
 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
@@ -516,7 +511,6 @@
 github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
-github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
 github.com/pires/go-proxyproto v0.6.0 h1:cLJUPnuQdiNf7P/wbeOKmM1khVdaMgTFDLj8h9ZrVYk=
 github.com/pires/go-proxyproto v0.6.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
@@ -713,7 +707,6 @@
 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
@@ -962,9 +955,7 @@
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
-gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
-gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1629338024 0
#      Thu Aug 19 01:53:44 2021 +0000
# Node ID d6626bbcf01234bb3c5625bd1f9fe14de80f4f08
# Parent  7164a9b6bb008b61362352124e85969d76d8d405
ci: remove go 1.15 test job

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -52,13 +52,6 @@
   script:
     - make verify test
 
-go:1.15:
-  extends: .test
-  image: golang:1.15
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
 go:1.16:
   extends: .test
   image: golang:1.16
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1629368304 0
#      Thu Aug 19 10:18:24 2021 +0000
# Node ID 6415f1b04fa7998f0c06b2a12b06d45eb15651ad
# Parent  c44eb8f53c8a45ecaf0f6a750ad793f56c248926
# Parent  d6626bbcf01234bb3c5625bd1f9fe14de80f4f08
Merge branch 'bump/go-1.16' into 'main'

build: bump go version to 1.16

See merge request gitlab-org/gitlab-shell!509

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -52,13 +52,6 @@
   script:
     - make verify test
 
-go:1.15:
-  extends: .test
-  image: golang:1.15
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
 go:1.16:
   extends: .test
   image: golang:1.16
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module gitlab.com/gitlab-org/gitlab-shell
 
-go 1.15
+go 1.16
 
 require (
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -162,7 +162,6 @@
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
 github.com/getsentry/raven-go v0.1.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
@@ -180,7 +179,6 @@
 github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
 github.com/git-lfs/wildmatch v1.0.4/go.mod h1:SdHAGnApDpnFYQ0vAxbniWR0sn7yLJ3QXo9RRfhn2ew=
 github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
-github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -268,10 +266,8 @@
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -498,7 +494,6 @@
 github.com/otiai10/copy v1.4.2 h1:RTiz2sol3eoXPLF4o+YWqEybwfUa/Q2Nkc4ZIUs3fwI=
 github.com/otiai10/copy v1.4.2/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E=
 github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
-github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI=
 github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
 github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw=
 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
@@ -516,7 +511,6 @@
 github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
-github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
 github.com/pires/go-proxyproto v0.6.0 h1:cLJUPnuQdiNf7P/wbeOKmM1khVdaMgTFDLj8h9ZrVYk=
 github.com/pires/go-proxyproto v0.6.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
@@ -713,7 +707,6 @@
 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
@@ -962,9 +955,7 @@
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
-gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
-gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1629905872 -3600
#      Wed Aug 25 16:37:52 2021 +0100
# Node ID ae635775668d803f836b4227e42b500037db32e4
# Parent  6415f1b04fa7998f0c06b2a12b06d45eb15651ad
Add missing CHANGELOG entries

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,6 +6,10 @@
 - Fix the Geo SSH push proxy hanging !487
 - Standardize logging timestamp format !485
 
+v13.19.1
+
+- Modify regex to prevent partial matches
+
 v13.19.0
 
 - Don't finish the opentracing span early !466
@@ -13,6 +17,10 @@
 - Stop changing directory to the filesystem root !470
 - Fix opentracing setup for gitlab-sshd !473
 
+v13.18.1
+
+- Modify regex to prevent partial matches
+
 v13.18.0
 
 - Fix thread-safety issues in gitlab-shell !463
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1629905884 -3600
#      Wed Aug 25 16:38:04 2021 +0100
# Node ID f8a1d5432aebce2dc98f5e17722235818e47d9b3
# Parent  ae635775668d803f836b4227e42b500037db32e4
Version 3.21.0

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,13 @@
+v13.21.0
+
+- Change default logging format to JSON !476
+- Shutdown sshd gracefully !484
+- Provide liveness and readiness probes !494
+- Add tracing instrumentation to http client !495
+- Log same correlation_id on auth keys check of ssh connections !501
+- fix: validate client cert paths exist on disk before proceeding !508
+- Modify regex to prevent partial matches
+
 v13.20.0
 
 - Remove bin/authorized_keys !491
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.20.0
+13.21.0
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1629906785 0
#      Wed Aug 25 15:53:05 2021 +0000
# Node ID 854da4efdb45866ea0259ffcd156c46f5f8b238b
# Parent  6415f1b04fa7998f0c06b2a12b06d45eb15651ad
# Parent  f8a1d5432aebce2dc98f5e17722235818e47d9b3
Merge branch 'release-13-21' into 'main'

Release v13.21.0

See merge request gitlab-org/gitlab-shell!512

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,13 @@
+v13.21.0
+
+- Change default logging format to JSON !476
+- Shutdown sshd gracefully !484
+- Provide liveness and readiness probes !494
+- Add tracing instrumentation to http client !495
+- Log same correlation_id on auth keys check of ssh connections !501
+- fix: validate client cert paths exist on disk before proceeding !508
+- Modify regex to prevent partial matches
+
 v13.20.0
 
 - Remove bin/authorized_keys !491
@@ -6,6 +16,10 @@
 - Fix the Geo SSH push proxy hanging !487
 - Standardize logging timestamp format !485
 
+v13.19.1
+
+- Modify regex to prevent partial matches
+
 v13.19.0
 
 - Don't finish the opentracing span early !466
@@ -13,6 +27,10 @@
 - Stop changing directory to the filesystem root !470
 - Fix opentracing setup for gitlab-sshd !473
 
+v13.18.1
+
+- Modify regex to prevent partial matches
+
 v13.18.0
 
 - Fix thread-safety issues in gitlab-shell !463
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.20.0
+13.21.0
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1632383296 0
#      Thu Sep 23 07:48:16 2021 +0000
# Node ID da6ed6ed7edcb351d82790a413d3edab8e991c10
# Parent  854da4efdb45866ea0259ffcd156c46f5f8b238b
Merge branch 'sh-fix-issue-529' into 'main'

Only validate SSL cert file exists if a value is supplied

See merge request gitlab-org/gitlab-shell!527

diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -55,6 +55,22 @@
 	}
 }
 
+func validateCaFile(filename string) error {
+	if filename == "" {
+		return nil
+	}
+
+	if _, err := os.Stat(filename); err != nil {
+		if os.IsNotExist(err) {
+			return fmt.Errorf("cannot find cafile '%s': %w", filename, ErrCafileNotFound)
+		}
+
+		return err
+	}
+
+	return nil
+}
+
 // Deprecated: use NewHTTPClientWithOpts - https://gitlab.com/gitlab-org/gitlab-shell/-/issues/484
 func NewHTTPClient(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64) *HttpClient {
 	c, err := NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath, selfSignedCert, readTimeoutSeconds, nil)
@@ -74,10 +90,8 @@
 	} else if strings.HasPrefix(gitlabURL, httpProtocol) {
 		transport, host = buildHttpTransport(gitlabURL)
 	} else if strings.HasPrefix(gitlabURL, httpsProtocol) {
-		if _, err := os.Stat(caFile); err != nil {
-			if os.IsNotExist(err) {
-				return nil, fmt.Errorf("cannot find cafile '%s': %w", caFile, ErrCafileNotFound)
-			}
+		err = validateCaFile(caFile)
+		if err != nil {
 			return nil, err
 		}
 
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -66,10 +66,11 @@
 
 func TestFailedRequests(t *testing.T) {
 	testCases := []struct {
-		desc          string
-		caFile        string
-		caPath        string
-		expectedError string
+		desc                   string
+		caFile                 string
+		caPath                 string
+		expectedCaFileNotFound bool
+		expectedError          string
 	}{
 		{
 			desc:          "Invalid CaFile",
@@ -77,18 +78,25 @@
 			expectedError: "Internal API unreachable",
 		},
 		{
-			desc:   "Invalid CaPath",
-			caPath: path.Join(testhelper.TestRoot, "certs/invalid"),
+			desc:                   "Missing CaFile",
+			caFile:                 path.Join(testhelper.TestRoot, "certs/invalid/missing.crt"),
+			expectedCaFileNotFound: true,
 		},
 		{
-			desc: "Empty config",
+			desc:          "Invalid CaPath",
+			caPath:        path.Join(testhelper.TestRoot, "certs/invalid"),
+			expectedError: "Internal API unreachable",
+		},
+		{
+			desc:          "Empty config",
+			expectedError: "Internal API unreachable",
 		},
 	}
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
 			client, err := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "", false)
-			if tc.caFile == "" {
+			if tc.expectedCaFileNotFound {
 				require.Error(t, err)
 				require.ErrorIs(t, err, ErrCafileNotFound)
 			} else {
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1632384319 25200
#      Thu Sep 23 01:05:19 2021 -0700
# Node ID a32928a56da5398ddec1d2b2f2df48e07863e2fe
# Parent  da6ed6ed7edcb351d82790a413d3edab8e991c10
Release v13.21.1

This brings in
https://gitlab.com/gitlab-org/gitlab-shell/-/merge_requests/527 to fix
SSL certs not working when HTTPS is used for the internal API.

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v13.21.1
+
+- Only validate SSL cert file exists if a value is supplied !527
+
 v13.21.0
 
 - Change default logging format to JSON !476
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.21.0
+13.21.1
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1632385264 0
#      Thu Sep 23 08:21:04 2021 +0000
# Node ID 5b532c297ecf2e1ee5819a6ab61bbee647a6c904
# Parent  da6ed6ed7edcb351d82790a413d3edab8e991c10
# Parent  a32928a56da5398ddec1d2b2f2df48e07863e2fe
Merge branch 'sh-release-13-21-1' into '13-21-stable'

Release v13.21.1

See merge request gitlab-org/gitlab-shell!529

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v13.21.1
+
+- Only validate SSL cert file exists if a value is supplied !527
+
 v13.21.0
 
 - Change default logging format to JSON !476
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.21.0
+13.21.1
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1636629399 -3600
#      Thu Nov 11 12:16:39 2021 +0100
# Branch heptapod-stable
# Node ID 29ff795406b8b544ad4be6b1dcbc3f119b955a36
# Parent  23b11deb9b862507063f1da21be251b61a7d4fb4
# Parent  837e1b491f17f9d8f8e2c0e5bb5519de76b6c4fb
heptapod#574: making Heptapod 0.26 the new stable

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -34,3 +34,4 @@
 3cfed32ac2038975b533352ff0f4839aa2a0a319 heptapod-13.18.1
 aefbb5e3eac5411fc526a88d3df1762ecbdc2e76 heptapod-13.19.0
 b84ce63181d5c0d07585be867dbe957eb6d35348 heptapod-13.19.1
+f593b81abfe52ca2083440f3f470dce3d13079da heptapod-13.19.2
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -9,6 +9,11 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 13.19.2
+
+- Added support for the new `NO_GIT` flag, telling Mercurial not to
+  use hg-git
+
 ## 13.19.1
 
 - No actual changes
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-13.19.1
+13.19.2
diff --git a/internal/command/hg/hg.go b/internal/command/hg/hg.go
--- a/internal/command/hg/hg.go
+++ b/internal/command/hg/hg.go
@@ -80,6 +80,7 @@
 		"HEPTAPOD_REPOSITORY_USAGE=" + strings.Split(response.Repo, "-")[0],
 		// FormatBool output is "true" or "false", understood by hg config
 		"HEPTAPOD_HG_NATIVE=" + strconv.FormatBool(response.HgNative),
+		"HEPTAPOD_NO_GIT=" + strconv.FormatBool(response.NoGit),
 	}...)
 	if c.Config.Mercurial.ModulePolicy != "" {
 		env = append(env, "HGMODULEPOLICY=" + c.Config.Mercurial.ModulePolicy)
diff --git a/internal/command/hg/hg_test.go b/internal/command/hg/hg_test.go
--- a/internal/command/hg/hg_test.go
+++ b/internal/command/hg/hg_test.go
@@ -72,7 +72,9 @@
 func TestExecuteSuccessfully(t *testing.T) {
 	type testcase struct {
 		native_input    *bool
+		no_git_input    *bool
 		native_expected string
+		no_git_expected string
 		module_policy   string
 	}
 
@@ -81,14 +83,17 @@
 	yes := true;
 
 	testcases := []testcase{
-		{native_input: nil, native_expected: "false", module_policy: "c"},
-		{native_input: &no, native_expected: "false", module_policy: ""},
-		{native_input: &yes, native_expected: "true"},
+		{native_input: nil, native_expected: "false", no_git_expected: "false", module_policy: "c"},
+		{native_input: &no, native_expected: "false", no_git_expected: "false", module_policy: ""},
+		{native_input: &yes, native_expected: "true", no_git_expected: "false"},
+		{native_input: nil, no_git_input: &yes, native_expected: "false", no_git_expected: "true", module_policy: "c"},
+		{native_input: &no, no_git_input: &yes, native_expected: "false", no_git_expected: "true", module_policy: ""},
+		{native_input: &yes, native_expected: "true", no_git_input: &yes, no_git_expected: "true"},
 	}
 
 	for _, tc := range testcases {
 
-		requests := requesthandlers.BuildHgApiSuccessHandlers(t, tc.native_input)
+		requests := requesthandlers.BuildHgApiSuccessHandlers(t, tc.native_input, tc.no_git_input)
 		url := testserver.StartHttpServer(t, requests)
 
 		output := &bytes.Buffer{}
@@ -128,6 +133,7 @@
 		require.Equal(t, "awesome-proj", fake.Env["HEPTAPOD_PROJECT_PATH"])
 		require.Equal(t, "my/group", fake.Env["HEPTAPOD_PROJECT_NAMESPACE_FULL_PATH"])
 		require.Equal(t, tc.native_expected, fake.Env["HEPTAPOD_HG_NATIVE"])
+		require.Equal(t, tc.no_git_expected, fake.Env["HEPTAPOD_NO_GIT"])
 		require.Equal(t, "/usr/share/shipped.hgrc:/etc/main.hgrc", fake.Env["HGRCPATH"])
 		require.Equal(t, tc.module_policy, fake.Env["HGMODULEPOLICY"])
 	}
@@ -135,7 +141,7 @@
 
 func TestIgnoreHarmfulClientArgs(t *testing.T) {
 
-	requests := requesthandlers.BuildHgApiSuccessHandlers(t, nil)
+	requests := requesthandlers.BuildHgApiSuccessHandlers(t, nil, nil)
 	url := testserver.StartHttpServer(t, requests)
 
 	output := &bytes.Buffer{}
diff --git a/internal/gitlabnet/accessverifier/hg_client.go b/internal/gitlabnet/accessverifier/hg_client.go
--- a/internal/gitlabnet/accessverifier/hg_client.go
+++ b/internal/gitlabnet/accessverifier/hg_client.go
@@ -33,6 +33,7 @@
 	Email      string `json:"gl_email"`
 	ProjectId  int    `json:"project_id"`
 	HgNative   bool   `json:"hg_native"`
+	NoGit      bool   `json:"no_git"`
 	StatusCode int
 }
 
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -50,7 +50,7 @@
 	return requests
 }
 
-func BuildHgApiSuccessHandlers(t *testing.T, hg_native *bool) []testserver.TestRequestHandler {
+func BuildHgApiSuccessHandlers(t *testing.T, hg_native *bool, no_git *bool) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/hg_access",
@@ -68,6 +68,9 @@
 				if (hg_native != nil) {
 					body["hg_native"] = *hg_native
 				}
+				if (no_git != nil) {
+					body["no_git"] = *no_git
+				}
 				w.WriteHeader(http.StatusOK)
 				require.NoError(t, json.NewEncoder(w).Encode(body))
 			},
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1636323814 -3600
#      Sun Nov 07 23:23:34 2021 +0100
# Branch heptapod
# Node ID f23dd26963eecab3f2dc56638921336cc5e88db2
# Parent  837e1b491f17f9d8f8e2c0e5bb5519de76b6c4fb
# Parent  5b532c297ecf2e1ee5819a6ab61bbee647a6c904
Merged upstream v13.21.1

Just had to adapt the setup for hg_client_test.go because
of a test helper API change and rerun `make fmt` (new rule,
it seems).

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -52,20 +52,6 @@
   script:
     - make verify test
 
-go:1.14:
-  extends: .test
-  image: golang:1.14
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
-go:1.15:
-  extends: .test
-  image: golang:1.15
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
 go:1.16:
   extends: .test
   image: golang:1.16
@@ -73,9 +59,16 @@
     - make coverage
   coverage: '/\d+.\d+%/'
 
+go:1.17:
+  extends: .test
+  image: golang:1.17
+  after_script:
+    - make coverage
+  coverage: '/\d+.\d+%/'
+
 race:
   extends: .test
-  image: golang:1.16
+  image: golang:1.17
   script:
     - make test_golang_race
 
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,25 @@
+v13.21.1
+
+- Only validate SSL cert file exists if a value is supplied !527
+
+v13.21.0
+
+- Change default logging format to JSON !476
+- Shutdown sshd gracefully !484
+- Provide liveness and readiness probes !494
+- Add tracing instrumentation to http client !495
+- Log same correlation_id on auth keys check of ssh connections !501
+- fix: validate client cert paths exist on disk before proceeding !508
+- Modify regex to prevent partial matches
+
+v13.20.0
+
+- Remove bin/authorized_keys !491
+- Add a make install command !490
+- Create PROCESS.md page with Security release process !488
+- Fix the Geo SSH push proxy hanging !487
+- Standardize logging timestamp format !485
+
 v13.19.1
 
 - Modify regex to prevent partial matches
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -9,6 +9,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 13.21.0
+
+- Jump to upstream GitLab Shell v13.21.1 (GitLab 14.4 series)
+
 ## 13.19.2
 
 - Added support for the new `NO_GIT` flag, telling Mercurial not to
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-13.19.2
+13.21.0
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: validate verify verify_ruby verify_golang test test_ruby test_golang coverage coverage_golang setup _install build compile check clean
+.PHONY: validate verify verify_ruby verify_golang test test_ruby test_golang coverage coverage_golang setup _script_install build compile check clean install
 
 GO_SOURCES := $(shell find . -name '*.go')
 VERSION_STRING := $(shell ./compute_version || echo unknown)
@@ -6,12 +6,17 @@
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)"
 
+PREFIX ?= /usr/local
+
 validate: verify test
 
 verify: verify_golang
 
 verify_golang:
-	gofmt -s -l $(GO_SOURCES)
+	gofmt -s -l $(GO_SOURCES) | awk '{ print } END { if (NR > 0) { print "Please run make fmt"; exit 1 } }'
+
+fmt:
+	gofmt -w -s $(GO_SOURCES)
 
 test: test_ruby test_golang
 
@@ -30,9 +35,9 @@
 coverage_golang:
 	[ -f cover.out ] && go tool cover -func cover.out
 
-setup: _install bin/gitlab-shell
+setup: _script_install bin/gitlab-shell
 
-_install:
+_script_install:
 	bin/install
 
 build: bin/gitlab-shell
@@ -45,3 +50,12 @@
 
 clean:
 	rm -f bin/check bin/gitlab-shell bin/gitlab-shell-authorized-keys-check bin/gitlab-shell-authorized-principals-check bin/gitlab-sshd
+
+install: compile
+	mkdir -p $(DESTDIR)$(PREFIX)/bin/
+	install -m755 bin/check $(DESTDIR)$(PREFIX)/bin/check
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-keys-check
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-principals-check
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-sshd
+
diff --git a/PROCESS.md b/PROCESS.md
new file mode 100644
--- /dev/null
+++ b/PROCESS.md
@@ -0,0 +1,44 @@
+## Releasing a new version
+
+GitLab Shell is versioned by git tags, and the version used by the Rails
+application is stored in
+[`GITLAB_SHELL_VERSION`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_SHELL_VERSION).
+
+For each version, there is a raw version and a tag version:
+
+- The **raw version** is the version number. For instance, `15.2.8`.
+- The **tag version** is the raw version prefixed with `v`. For instance, `v15.2.8`.
+
+To release a new version of GitLab Shell and have that version available to the
+Rails application:
+
+1. Create a merge request to update the [`CHANGELOG`](CHANGELOG) with the
+   **tag version** and the [`VERSION`](VERSION) file with the **raw version**.
+2. Ask a maintainer to review and merge the merge request. If you're already a
+   maintainer, second maintainer review is not required.
+3. Add a new git tag with the **tag version**.
+4. Update `GITLAB_SHELL_VERSION` in the Rails application to the **raw
+   version**. (Note: this can be done as a separate MR to that, or in and MR
+   that will make use of the latest GitLab Shell changes.)
+
+## Security releases
+
+GitLab Shell is included in the packages we create for GitLab, and each version of GitLab specifies the version of GitLab Shell it uses in the `GITLAB_SHELL_VERSION` file, so security fixes in GitLab Shell are tightly coupled to the [GitLab security release](https://about.gitlab.com/handbook/engineering/workflow/#security-issues) workflow.
+
+For a security fix in GitLab Shell, two sets of merge requests are required:
+
+* The fix itself, in the `gitlab-org/security/gitlab-shell` repository and its backports to the previous versions of GitLab Shell
+* Merge requests to change the versions of GitLab Shell included in the GitLab security release, in the `gitlab-org/security/gitlab` repository
+
+The first step could be to create a merge request with a fix targeting `main` in `gitlab-org/security/gitlab-shell`. When the merge request is approved by maintainers, backports targeting previous 3 versions of GitLab Shell must be created. The stable branches for those versions may not exist, feel free to ask a maintainer to create ones. The stable branches must be created out of the GitLab Shell tags/version used by the 3 previous GitLab releases. In order to find out the GitLab Shell version that is used on a particular GitLab stable release, the following steps may be helpful:
+
+```shell
+git fetch security 13-9-stable-ee
+git show refs/remotes/security/13-9-stable-ee:GITLAB_SHELL_VERSION
+```
+
+These steps display the version that is used by `13.9` version of GitLab.
+
+Close to the GitLab security release, a maintainer should merge the fix and backports and cut all the necessary GitLab Shell versions. This allows bumping the `GITLAB_SHELL_VERSION` for `gitlab-org/security/gitlab`. The GitLab merge request will be handled by the general GitLab security release process.
+
+Once the security release is done, a GitLab Shell maintainer is responsible for syncing tags and `main` to the `gitlab-org/gitlab-shell` repository.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
 
 When you access the GitLab server over SSH then GitLab Shell will:
 
-1. Limits you to predefined git commands (git push, git pull).
+1. Limit you to predefined git commands (git push, git pull).
 1. Call the GitLab Rails API to check if you are authorized, and what Gitaly server your repository is on
 1. Copy data back and forth between the SSH client and the Gitaly server
 
@@ -38,16 +38,36 @@
 We follow the [Golang Release Policy](https://golang.org/doc/devel/release.html#policy)
 of supporting the current stable version and the previous two major versions.
 
-## Setup
-
-    make setup
-
 ## Check
 
 Checks if GitLab API access and redis via internal API can be reached:
 
     make check
 
+## Compile
+
+Builds the `gitlab-shell` binaries, placing them into `bin/`.
+
+    make compile
+
+## Install
+
+Builds the `gitlab-shell` binaries and installs them onto the filesystem. The
+default location is `/usr/local`, but can be controlled by use of the `PREFIX`
+and `DESTDIR` environment variables.
+
+    make install
+
+## Setup
+
+This command is intended for use when installing GitLab from source on a single
+machine. In addition to compiling the gitlab-shell binaries, it ensures that
+various paths on the filesystem exist with the correct permissions. Do not run
+it unless instructed to by your installation method documentation.
+
+    make setup
+
+
 ## Testing
 
 Run tests:
@@ -82,28 +102,9 @@
 
 Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
 
-## Releasing a new version
-
-GitLab Shell is versioned by git tags, and the version used by the Rails
-application is stored in
-[`GITLAB_SHELL_VERSION`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_SHELL_VERSION).
-
-For each version, there is a raw version and a tag version:
-
-- The **raw version** is the version number. For instance, `15.2.8`.
-- The **tag version** is the raw version prefixed with `v`. For instance, `v15.2.8`.
+## Releasing
 
-To release a new version of GitLab Shell and have that version available to the
-Rails application:
-
-1. Create a merge request to update the [`CHANGELOG`](CHANGELOG) with the
-   **tag version** and the [`VERSION`](VERSION) file with the **raw version**.
-2. Ask a maintainer to review and merge the merge request. If you're already a
-   maintainer, second maintainer review is not required.
-3. Add a new git tag with the **tag version**.
-4. Update `GITLAB_SHELL_VERSION` in the Rails application to the **raw
-   version**. (Note: this can be done as a separate MR to that, or in and MR
-   that will make use of the latest GitLab Shell changes.)
+See [PROCESS.md](./PROCESS.md)
 
 ## Contributing
 
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.19.1
+13.21.1
diff --git a/bin/authorized_keys b/bin/authorized_keys
deleted file mode 100755
--- a/bin/authorized_keys
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/sh
-
-# Legacy script used for AuthorizedKeysCommand when configured without username.
-# Executes gitlab-shell-authorized-keys-check with "git" as expected and actual
-# username and with the passed key.
-#
-# TODO: Remove this in https://gitlab.com/gitlab-org/gitlab-shell/issues/209.
-
-$(dirname $0)/gitlab-shell-authorized-keys-check git git $1
diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -11,16 +11,14 @@
 	"strings"
 	"testing"
 
-	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
+
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
 
 func TestClients(t *testing.T) {
-	testDirCleanup, err := testhelper.PrepareTestRootDir()
-	require.NoError(t, err)
-	defer testDirCleanup()
+	testhelper.PrepareTestRootDir(t)
 
 	testCases := []struct {
 		desc            string
@@ -61,7 +59,8 @@
 
 			secret := "sssh, it's a secret"
 
-			httpClient := NewHTTPClient(url, tc.relativeURLRoot, tc.caFile, "", false, 1)
+			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", false, 1, nil)
+			require.NoError(t, err)
 
 			client, err := NewGitlabNetClient("", "", secret, httpClient)
 			require.NoError(t, err)
@@ -78,7 +77,6 @@
 
 func testSuccessfulGet(t *testing.T, client *GitlabNetClient) {
 	t.Run("Successful get", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		response, err := client.Get(context.Background(), "/hello")
 		require.NoError(t, err)
 		require.NotNil(t, response)
@@ -88,22 +86,11 @@
 		responseBody, err := ioutil.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, string(responseBody), "Hello")
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=GET")
-		require.Contains(t, entries[0].Message, "status=200")
-		require.Contains(t, entries[0].Message, "content_length_bytes=")
-		require.Contains(t, entries[0].Message, "Finished HTTP request")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
 func testSuccessfulPost(t *testing.T, client *GitlabNetClient) {
 	t.Run("Successful Post", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		data := map[string]string{"key": "value"}
 
 		response, err := client.Post(context.Background(), "/post_endpoint", data)
@@ -115,50 +102,20 @@
 		responseBody, err := ioutil.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, "Echo: {\"key\":\"value\"}", string(responseBody))
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=POST")
-		require.Contains(t, entries[0].Message, "status=200")
-		require.Contains(t, entries[0].Message, "content_length_bytes=")
-		require.Contains(t, entries[0].Message, "Finished HTTP request")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
 func testMissing(t *testing.T, client *GitlabNetClient) {
 	t.Run("Missing error for GET", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		response, err := client.Get(context.Background(), "/missing")
 		require.EqualError(t, err, "Internal API error (404)")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=GET")
-		require.Contains(t, entries[0].Message, "status=404")
-		require.Contains(t, entries[0].Message, "Internal API error")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 
 	t.Run("Missing error for POST", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		response, err := client.Post(context.Background(), "/missing", map[string]string{})
 		require.EqualError(t, err, "Internal API error (404)")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=POST")
-		require.Contains(t, entries[0].Message, "status=404")
-		require.Contains(t, entries[0].Message, "Internal API error")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
@@ -178,37 +135,15 @@
 
 func testBrokenRequest(t *testing.T, client *GitlabNetClient) {
 	t.Run("Broken request for GET", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
-
 		response, err := client.Get(context.Background(), "/broken")
 		require.EqualError(t, err, "Internal API unreachable")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=GET")
-		require.NotContains(t, entries[0].Message, "status=")
-		require.Contains(t, entries[0].Message, "Internal API unreachable")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 
 	t.Run("Broken request for POST", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
-
 		response, err := client.Post(context.Background(), "/broken", map[string]string{})
 		require.EqualError(t, err, "Internal API unreachable")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=POST")
-		require.NotContains(t, entries[0].Message, "status=")
-		require.Contains(t, entries[0].Message, "Internal API unreachable")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -11,9 +11,7 @@
 	"strings"
 	"time"
 
-	"gitlab.com/gitlab-org/labkit/correlation"
-
-	log "github.com/sirupsen/logrus"
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
@@ -71,12 +69,12 @@
 	return path
 }
 
-func newRequest(ctx context.Context, method, host, path string, data interface{}) (*http.Request, string, error) {
+func newRequest(ctx context.Context, method, host, path string, data interface{}) (*http.Request, error) {
 	var jsonReader io.Reader
 	if data != nil {
 		jsonData, err := json.Marshal(data)
 		if err != nil {
-			return nil, "", err
+			return nil, err
 		}
 
 		jsonReader = bytes.NewReader(jsonData)
@@ -84,12 +82,10 @@
 
 	request, err := http.NewRequestWithContext(ctx, method, host+path, jsonReader)
 	if err != nil {
-		return nil, "", err
+		return nil, err
 	}
 
-	correlationID := correlation.ExtractFromContext(ctx)
-
-	return request, correlationID, nil
+	return request, nil
 }
 
 func parseError(resp *http.Response) error {
@@ -116,7 +112,7 @@
 }
 
 func (c *GitlabNetClient) DoRequest(ctx context.Context, method, path string, data interface{}) (*http.Response, error) {
-	request, correlationID, err := newRequest(ctx, method, c.httpClient.Host, path, data)
+	request, err := newRequest(ctx, method, c.httpClient.Host, path, data)
 	if err != nil {
 		return nil, err
 	}
@@ -136,12 +132,11 @@
 	start := time.Now()
 	response, err := c.httpClient.Do(request)
 	fields := log.Fields{
-		"correlation_id": correlationID,
-		"method":         method,
-		"url":            request.URL.String(),
-		"duration_ms":    time.Since(start) / time.Millisecond,
+		"method":      method,
+		"url":         request.URL.String(),
+		"duration_ms": time.Since(start) / time.Millisecond,
 	}
-	logger := log.WithFields(fields)
+	logger := log.WithContextFields(ctx, fields)
 
 	if err != nil {
 		logger.WithError(err).Error("Internal API unreachable")
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -5,15 +5,18 @@
 	"crypto/tls"
 	"crypto/x509"
 	"errors"
+	"fmt"
 	"io/ioutil"
 	"net"
 	"net/http"
+	"os"
 	"path/filepath"
 	"strings"
 	"time"
 
-	log "github.com/sirupsen/logrus"
 	"gitlab.com/gitlab-org/labkit/correlation"
+	"gitlab.com/gitlab-org/labkit/log"
+	"gitlab.com/gitlab-org/labkit/tracing"
 )
 
 const (
@@ -24,6 +27,10 @@
 	defaultReadTimeoutSeconds = 300
 )
 
+var (
+	ErrCafileNotFound = errors.New("cafile not found")
+)
+
 type HttpClient struct {
 	*http.Client
 	Host string
@@ -48,6 +55,22 @@
 	}
 }
 
+func validateCaFile(filename string) error {
+	if filename == "" {
+		return nil
+	}
+
+	if _, err := os.Stat(filename); err != nil {
+		if os.IsNotExist(err) {
+			return fmt.Errorf("cannot find cafile '%s': %w", filename, ErrCafileNotFound)
+		}
+
+		return err
+	}
+
+	return nil
+}
+
 // Deprecated: use NewHTTPClientWithOpts - https://gitlab.com/gitlab-org/gitlab-shell/-/issues/484
 func NewHTTPClient(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64) *HttpClient {
 	c, err := NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath, selfSignedCert, readTimeoutSeconds, nil)
@@ -59,15 +82,6 @@
 
 // NewHTTPClientWithOpts builds an HTTP client using the provided options
 func NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64, opts []HTTPClientOpt) (*HttpClient, error) {
-	hcc := &httpClientCfg{
-		caFile: caFile,
-		caPath: caPath,
-	}
-
-	for _, opt := range opts {
-		opt(hcc)
-	}
-
 	var transport *http.Transport
 	var host string
 	var err error
@@ -76,6 +90,20 @@
 	} else if strings.HasPrefix(gitlabURL, httpProtocol) {
 		transport, host = buildHttpTransport(gitlabURL)
 	} else if strings.HasPrefix(gitlabURL, httpsProtocol) {
+		err = validateCaFile(caFile)
+		if err != nil {
+			return nil, err
+		}
+
+		hcc := &httpClientCfg{
+			caFile: caFile,
+			caPath: caPath,
+		}
+
+		for _, opt := range opts {
+			opt(hcc)
+		}
+
 		transport, host, err = buildHttpsTransport(*hcc, selfSignedCert, gitlabURL)
 		if err != nil {
 			return nil, err
@@ -85,7 +113,7 @@
 	}
 
 	c := &http.Client{
-		Transport: correlation.NewInstrumentedRoundTripper(transport),
+		Transport: correlation.NewInstrumentedRoundTripper(tracing.NewRoundTripper(transport)),
 		Timeout:   readTimeout(readTimeoutSeconds),
 	}
 
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -17,7 +17,8 @@
 func TestReadTimeout(t *testing.T) {
 	expectedSeconds := uint64(300)
 
-	client := NewHTTPClient("http://localhost:3000", "", "", "", false, expectedSeconds)
+	client, err := NewHTTPClientWithOpts("http://localhost:3000", "", "", "", false, expectedSeconds, nil)
+	require.NoError(t, err)
 
 	require.NotNil(t, client)
 	require.Equal(t, time.Duration(expectedSeconds)*time.Second, client.Client.Timeout)
@@ -122,7 +123,8 @@
 func setup(t *testing.T, username, password string, requests []testserver.TestRequestHandler) *GitlabNetClient {
 	url := testserver.StartHttpServer(t, requests)
 
-	httpClient := NewHTTPClient(url, "", "", "", false, 1)
+	httpClient, err := NewHTTPClientWithOpts(url, "", "", "", false, 1, nil)
+	require.NoError(t, err)
 
 	client, err := NewGitlabNetClient(username, password, "", httpClient)
 	require.NoError(t, err)
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -28,10 +28,7 @@
 		{
 			desc:   "Valid CaPath",
 			caPath: path.Join(testhelper.TestRoot, "certs/valid"),
-		},
-		{
-			desc:       "Self signed cert option enabled",
-			selfSigned: true,
+			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
 		},
 		{
 			desc:       "Invalid cert with self signed cert option enabled",
@@ -51,7 +48,8 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath, tc.selfSigned)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath, tc.selfSigned)
+			require.NoError(t, err)
 
 			response, err := client.Get(context.Background(), "/hello")
 			require.NoError(t, err)
@@ -68,39 +66,51 @@
 
 func TestFailedRequests(t *testing.T) {
 	testCases := []struct {
-		desc   string
-		caFile string
-		caPath string
+		desc                   string
+		caFile                 string
+		caPath                 string
+		expectedCaFileNotFound bool
+		expectedError          string
 	}{
 		{
-			desc:   "Invalid CaFile",
-			caFile: path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+			desc:          "Invalid CaFile",
+			caFile:        path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+			expectedError: "Internal API unreachable",
 		},
 		{
-			desc:   "Invalid CaPath",
-			caPath: path.Join(testhelper.TestRoot, "certs/invalid"),
+			desc:                   "Missing CaFile",
+			caFile:                 path.Join(testhelper.TestRoot, "certs/invalid/missing.crt"),
+			expectedCaFileNotFound: true,
 		},
 		{
-			desc: "Empty config",
+			desc:          "Invalid CaPath",
+			caPath:        path.Join(testhelper.TestRoot, "certs/invalid"),
+			expectedError: "Internal API unreachable",
+		},
+		{
+			desc:          "Empty config",
+			expectedError: "Internal API unreachable",
 		},
 	}
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "", false)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "", false)
+			if tc.expectedCaFileNotFound {
+				require.Error(t, err)
+				require.ErrorIs(t, err, ErrCafileNotFound)
+			} else {
+				_, err = client.Get(context.Background(), "/hello")
+				require.Error(t, err)
 
-			_, err := client.Get(context.Background(), "/hello")
-			require.Error(t, err)
-
-			require.Equal(t, err.Error(), "Internal API unreachable")
+				require.Equal(t, err.Error(), tc.expectedError)
+			}
 		})
 	}
 }
 
-func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string, selfSigned bool) *GitlabNetClient {
-	testDirCleanup, err := testhelper.PrepareTestRootDir()
-	require.NoError(t, err)
-	defer testDirCleanup()
+func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string, selfSigned bool) (*GitlabNetClient, error) {
+	testhelper.PrepareTestRootDir(t)
 
 	requests := []testserver.TestRequestHandler{
 		{
@@ -121,10 +131,11 @@
 	}
 
 	httpClient, err := NewHTTPClientWithOpts(url, "", caFile, caPath, selfSigned, 1, opts)
-	require.NoError(t, err)
+	if err != nil {
+		return nil, err
+	}
 
 	client, err := NewGitlabNetClient("", "", "", httpClient)
-	require.NoError(t, err)
 
-	return client
+	return client, err
 }
diff --git a/cmd/check/main.go b/cmd/check/main.go
--- a/cmd/check/main.go
+++ b/cmd/check/main.go
@@ -31,7 +31,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
 	if err != nil {
diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go
--- a/cmd/gitlab-shell-authorized-keys-check/main.go
+++ b/cmd/gitlab-shell-authorized-keys-check/main.go
@@ -32,7 +32,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
 	if err != nil {
diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go
--- a/cmd/gitlab-shell-authorized-principals-check/main.go
+++ b/cmd/gitlab-shell-authorized-principals-check/main.go
@@ -32,7 +32,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
 	if err != nil {
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -46,7 +46,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	env := sshenv.NewFromEnv()
 	cmd, err := command.New(executable, os.Args[1:], env, config, readWriter)
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -95,9 +95,7 @@
 	t.Helper()
 
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		testDirCleanup, err := testhelper.PrepareTestRootDir()
-		require.NoError(t, err)
-		defer testDirCleanup()
+		testhelper.PrepareTestRootDir(t)
 
 		t.Logf("gitlab-api-mock: received request: %s %s", r.Method, r.RequestURI)
 		w.Header().Set("Content-Type", "application/json")
@@ -231,7 +229,7 @@
 	t.Cleanup(func() { pw.Close() })
 
 	scanner := bufio.NewScanner(pr)
-	extractor := regexp.MustCompile(`msg="Listening on ([0-9a-f\[\]\.:]+)"`)
+	extractor := regexp.MustCompile(`"tcp_address":"([0-9a-f\[\]\.:]+)"`)
 
 	ctx, cancel := context.WithCancel(context.Background())
 	cmd := exec.CommandContext(ctx, sshdPath, "-config-dir", dir)
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -1,15 +1,19 @@
 package main
 
 import (
+	"context"
 	"flag"
 	"os"
-
-	log "github.com/sirupsen/logrus"
+	"os/signal"
+	"syscall"
+	"time"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshd"
+
+	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/monitoring"
 )
 
@@ -45,37 +49,65 @@
 		var err error
 		cfg, err = config.NewFromDir(*configDir)
 		if err != nil {
-			log.Fatalf("failed to load configuration from specified directory: %v", err)
+			log.WithError(err).Fatal("failed to load configuration from specified directory")
 		}
 	}
 	overrideConfigFromEnvironment(cfg)
 	if err := cfg.IsSane(); err != nil {
 		if *configDir == "" {
-			log.Warn("note: no config-dir provided, using only environment variables")
+			log.WithError(err).Fatal("no config-dir provided, using only environment variables")
+		} else {
+			log.WithError(err).Fatal("configuration error")
 		}
-		log.Fatalf("configuration error: %v", err)
 	}
 
 	cfg.ApplyGlobalState()
 
-	logger.ConfigureStandalone(cfg)
+	logCloser := logger.ConfigureStandalone(cfg)
+	defer logCloser.Close()
 
 	ctx, finished := command.Setup("gitlab-sshd", cfg)
 	defer finished()
 
+	server, err := sshd.NewServer(cfg)
+	if err != nil {
+		log.WithError(err).Fatal("Failed to start GitLab built-in sshd")
+	}
+
 	// Startup monitoring endpoint.
 	if cfg.Server.WebListen != "" {
 		go func() {
-			log.Fatal(
-				monitoring.Start(
-					monitoring.WithListenerAddress(cfg.Server.WebListen),
-					monitoring.WithBuildInformation(Version, BuildTime),
-				),
+			err := monitoring.Start(
+				monitoring.WithListenerAddress(cfg.Server.WebListen),
+				monitoring.WithBuildInformation(Version, BuildTime),
+				monitoring.WithServeMux(server.MonitoringServeMux()),
 			)
+
+			log.WithError(err).Fatal("monitoring service raised an error")
 		}()
 	}
 
-	if err := sshd.Run(ctx, cfg); err != nil {
-		log.Fatalf("Failed to start GitLab built-in sshd: %v", err)
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	done := make(chan os.Signal, 1)
+	signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
+
+	go func() {
+		sig := <-done
+		signal.Reset(syscall.SIGINT, syscall.SIGTERM)
+
+		log.WithFields(log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
+
+		server.Shutdown()
+
+		<-time.After(cfg.Server.GracePeriod())
+
+		cancel()
+
+	}()
+
+	if err := server.ListenAndServe(ctx); err != nil {
+		log.WithError(err).Fatal("GitLab built-in sshd failed to listen for new connections")
 	}
 }
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -66,7 +66,7 @@
 # Log level. INFO by default
 log_level: INFO
 
-# Log format. 'text' by default
+# Log format. 'json' by default, can be changed to 'text' if needed
 # log_format: json
 
 # Audit usernames.
@@ -89,7 +89,7 @@
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
-  # SSH host key files. 
+  # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
     - /run/secrets/ssh-hostkeys/ssh_host_ecdsa_key
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module gitlab.com/gitlab-org/gitlab-shell
 
-go 1.13
+go 1.16
 
 require (
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
@@ -8,12 +8,11 @@
 	github.com/mattn/go-shellwords v1.0.11
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
-	github.com/pires/go-proxyproto v0.5.0
+	github.com/pires/go-proxyproto v0.6.0
 	github.com/prometheus/client_golang v1.10.0
-	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1
-	gitlab.com/gitlab-org/labkit v1.4.1
+	gitlab.com/gitlab-org/labkit v1.7.0
 	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/grpc v1.37.0
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -16,6 +16,7 @@
 cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
 cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
 cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
 cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
@@ -38,6 +39,8 @@
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -81,6 +84,8 @@
 github.com/avast/retry-go v2.4.2+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.37.0 h1:GzFnhOIsrGyQ69s7VgqtrG2BG8v7X7vwB3Xpbd/DBBk=
+github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -91,6 +96,8 @@
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 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=
 github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -155,7 +162,6 @@
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
 github.com/getsentry/raven-go v0.1.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
@@ -173,7 +179,6 @@
 github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
 github.com/git-lfs/wildmatch v1.0.4/go.mod h1:SdHAGnApDpnFYQ0vAxbniWR0sn7yLJ3QXo9RRfhn2ew=
 github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
-github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -212,8 +217,9 @@
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -260,10 +266,8 @@
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -274,6 +278,7 @@
 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210125172800-10e9aeb4a998/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo=
@@ -343,6 +348,10 @@
 github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
 github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@@ -485,7 +494,6 @@
 github.com/otiai10/copy v1.4.2 h1:RTiz2sol3eoXPLF4o+YWqEybwfUa/Q2Nkc4ZIUs3fwI=
 github.com/otiai10/copy v1.4.2/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E=
 github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
-github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI=
 github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
 github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw=
 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
@@ -503,10 +511,9 @@
 github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
-github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
-github.com/pires/go-proxyproto v0.5.0 h1:A4Jv4ZCaV3AFJeGh5mGwkz4iuWUYMlQ7IoO/GTuSuLo=
-github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pires/go-proxyproto v0.6.0 h1:cLJUPnuQdiNf7P/wbeOKmM1khVdaMgTFDLj8h9ZrVYk=
+github.com/pires/go-proxyproto v0.6.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -645,8 +652,8 @@
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
-gitlab.com/gitlab-org/labkit v1.4.1 h1:mh4g+c/esQSWCVQJ197aftrIbXlhXCtiLC8RgSvyCx0=
-gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
+gitlab.com/gitlab-org/labkit v1.7.0 h1:ufG42cvJ+VkqaUWVEoat/h9PGcW9FFSUPQFz7uwL5wc=
+gitlab.com/gitlab-org/labkit v1.7.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
@@ -657,6 +664,7 @@
 go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -699,7 +707,6 @@
 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
@@ -771,6 +778,7 @@
 golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
@@ -783,6 +791,7 @@
 golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
@@ -937,6 +946,7 @@
 golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -945,9 +955,7 @@
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
-gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
-gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
@@ -969,6 +977,7 @@
 google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
 google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.37.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
@@ -1020,6 +1029,8 @@
 google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
diff --git a/internal/command/hg/hg.go b/internal/command/hg/hg.go
--- a/internal/command/hg/hg.go
+++ b/internal/command/hg/hg.go
@@ -83,7 +83,7 @@
 		"HEPTAPOD_NO_GIT=" + strconv.FormatBool(response.NoGit),
 	}...)
 	if c.Config.Mercurial.ModulePolicy != "" {
-		env = append(env, "HGMODULEPOLICY=" + c.Config.Mercurial.ModulePolicy)
+		env = append(env, "HGMODULEPOLICY="+c.Config.Mercurial.ModulePolicy)
 	}
 
 	hg_exe := c.Config.Mercurial.BinPath()
diff --git a/internal/command/hg/hg_test.go b/internal/command/hg/hg_test.go
--- a/internal/command/hg/hg_test.go
+++ b/internal/command/hg/hg_test.go
@@ -79,8 +79,8 @@
 	}
 
 	// cannot directly take a reference to false and true
-	no := false;
-	yes := true;
+	no := false
+	yes := true
 
 	testcases := []testcase{
 		{native_input: nil, native_expected: "false", no_git_expected: "false", module_policy: "c"},
@@ -103,7 +103,7 @@
 				GitlabUrl: url,
 				Mercurial: config.MercurialConfig{
 					OptionalBinPath: "hg5.4-py3",
-					ModulePolicy: tc.module_policy,
+					ModulePolicy:    tc.module_policy,
 					Hgrc:            []string{"/usr/share/shipped.hgrc", "/etc/main.hgrc"}},
 			},
 			Args:       &commandargs.Shell{GitlabKeyId: "key-12", SshArgs: []string{"hg", "-R", "my/group/awesome-proj", "serve", "--stdio"}},
diff --git a/internal/command/receivepack/gitalycall_test.go b/internal/command/receivepack/gitalycall_test.go
--- a/internal/command/receivepack/gitalycall_test.go
+++ b/internal/command/receivepack/gitalycall_test.go
@@ -5,7 +5,6 @@
 	"context"
 	"testing"
 
-	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
@@ -14,12 +13,11 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
 )
 
 func TestReceivePack(t *testing.T) {
-	gitalyAddress, _ := testserver.StartGitalyServer(t)
+	gitalyAddress, testServer := testserver.StartGitalyServer(t)
 
 	requests := requesthandlers.BuildAllowedWithGitalyHandlers(t, gitalyAddress)
 	url := testserver.StartHttpServer(t, requests)
@@ -43,10 +41,15 @@
 
 		env := sshenv.Env{
 			IsSSHConnection: true,
-			OriginalCommand: "git-receive-pack group/repo",
+			OriginalCommand: "git-receive-pack " + repo,
 			RemoteAddr:      "127.0.0.1",
 		}
-		args := &commandargs.Shell{CommandType: commandargs.ReceivePack, SshArgs: []string{"git-receive-pack", repo}, Env: env}
+
+		args := &commandargs.Shell{
+			CommandType: commandargs.ReceivePack,
+			SshArgs:     []string{"git-receive-pack", repo},
+			Env:         env,
+		}
 
 		if tc.username != "" {
 			args.GitlabUsername = tc.username
@@ -60,7 +63,6 @@
 			ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 		}
 
-		hook := testhelper.SetupLogger()
 		ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 		ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
@@ -73,15 +75,20 @@
 			require.Equal(t, "ReceivePack: key-123 "+repo, output.String())
 		}
 
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 2, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[1].Level)
-		require.Contains(t, entries[1].Message, "executing git command")
-		require.Contains(t, entries[1].Message, "command=git-receive-pack")
-		require.Contains(t, entries[1].Message, "remote_ip=127.0.0.1")
-		require.Contains(t, entries[1].Message, "gl_key_type=key")
-		require.Contains(t, entries[1].Message, "gl_key_id=123")
-		require.Contains(t, entries[1].Message, "correlation_id=a-correlation-id")
+		for k, v := range map[string]string{
+			"gitaly-feature-cache_invalidator":        "true",
+			"gitaly-feature-inforef_uploadpack_cache": "false",
+			"x-gitlab-client-name":                    "gitlab-shell-tests-git-receive-pack",
+			"key_id":                                  "123",
+			"user_id":                                 "1",
+			"remote_ip":                               "127.0.0.1",
+			"key_type":                                "key",
+		} {
+			actual := testServer.ReceivedMD[k]
+			require.Len(t, actual, 1)
+			require.Equal(t, v, actual[0])
+		}
+		require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+		require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 	}
 }
diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -10,12 +10,13 @@
 	"io"
 	"net/http"
 
-	log "github.com/sirupsen/logrus"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/pktline"
+
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 type Request struct {
@@ -112,10 +113,32 @@
 }
 
 func (c *Command) readFromStdin() ([]byte, error) {
-	output := new(bytes.Buffer)
-	_, err := io.Copy(output, c.ReadWriter.In)
+	var output []byte
+	var needsPackData bool
+
+	scanner := pktline.NewScanner(c.ReadWriter.In)
+	for scanner.Scan() {
+		line := scanner.Bytes()
+		output = append(output, line...)
+
+		if pktline.IsFlush(line) {
+			break
+		}
 
-	return output.Bytes(), err
+		if !needsPackData && !pktline.IsRefRemoval(line) {
+			needsPackData = true
+		}
+	}
+
+	if needsPackData {
+		packData := new(bytes.Buffer)
+		_, err := io.Copy(packData, c.ReadWriter.In)
+
+		output = append(output, packData.Bytes()...)
+		return output, err
+	} else {
+		return output, nil
+	}
 }
 
 func (c *Command) readFromStdinNoEOF() []byte {
diff --git a/internal/command/shared/customaction/customaction_test.go b/internal/command/shared/customaction/customaction_test.go
--- a/internal/command/shared/customaction/customaction_test.go
+++ b/internal/command/shared/customaction/customaction_test.go
@@ -46,7 +46,7 @@
 				require.NoError(t, json.Unmarshal(b, &request))
 
 				require.Equal(t, request.Data.UserId, who)
-				require.Equal(t, "input", string(request.Output))
+				require.Equal(t, "0009input", string(request.Output))
 
 				err = json.NewEncoder(w).Encode(Response{Result: []byte("output")})
 				require.NoError(t, err)
@@ -58,7 +58,7 @@
 
 	outBuf := &bytes.Buffer{}
 	errBuf := &bytes.Buffer{}
-	input := bytes.NewBufferString("input")
+	input := bytes.NewBufferString("0009input")
 
 	response := &accessverifier.Response{
 		Who: who,
diff --git a/internal/command/uploadarchive/gitalycall_test.go b/internal/command/uploadarchive/gitalycall_test.go
--- a/internal/command/uploadarchive/gitalycall_test.go
+++ b/internal/command/uploadarchive/gitalycall_test.go
@@ -5,7 +5,6 @@
 	"context"
 	"testing"
 
-	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
@@ -13,12 +12,12 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
 )
 
-func TestUploadPack(t *testing.T) {
-	gitalyAddress, _ := testserver.StartGitalyServer(t)
+func TestUploadArchive(t *testing.T) {
+	gitalyAddress, testServer := testserver.StartGitalyServer(t)
 
 	requests := requesthandlers.BuildAllowedWithGitalyHandlers(t, gitalyAddress)
 	url := testserver.StartHttpServer(t, requests)
@@ -29,13 +28,25 @@
 	userId := "1"
 	repo := "group/repo"
 
+	env := sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: "git-upload-archive " + repo,
+		RemoteAddr:      "127.0.0.1",
+	}
+
+	args := &commandargs.Shell{
+		GitlabKeyId: userId,
+		CommandType: commandargs.UploadArchive,
+		SshArgs:     []string{"git-upload-archive", repo},
+		Env:         env,
+	}
+
 	cmd := &Command{
 		Config:     &config.Config{GitlabUrl: url},
-		Args:       &commandargs.Shell{GitlabKeyId: userId, CommandType: commandargs.UploadArchive, SshArgs: []string{"git-upload-archive", repo}},
+		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
 
-	hook := testhelper.SetupLogger()
 	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
@@ -44,13 +55,19 @@
 
 	require.Equal(t, "UploadArchive: "+repo, output.String())
 
-	require.True(t, testhelper.WaitForLogEvent(hook))
-	entries := hook.AllEntries()
-	require.Equal(t, 2, len(entries))
-	require.Equal(t, logrus.InfoLevel, entries[1].Level)
-	require.Contains(t, entries[1].Message, "executing git command")
-	require.Contains(t, entries[1].Message, "command=git-upload-archive")
-	require.Contains(t, entries[1].Message, "gl_key_type=key")
-	require.Contains(t, entries[1].Message, "gl_key_id=123")
-	require.Contains(t, entries[1].Message, "correlation_id=a-correlation-id")
+	for k, v := range map[string]string{
+		"gitaly-feature-cache_invalidator":        "true",
+		"gitaly-feature-inforef_uploadpack_cache": "false",
+		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-archive",
+		"key_id":                                  "123",
+		"user_id":                                 "1",
+		"remote_ip":                               "127.0.0.1",
+		"key_type":                                "key",
+	} {
+		actual := testServer.ReceivedMD[k]
+		require.Len(t, actual, 1)
+		require.Equal(t, v, actual[0])
+	}
+	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 }
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -4,7 +4,6 @@
 	"bytes"
 	"context"
 	"testing"
-	"time"
 
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
@@ -13,7 +12,7 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
 )
 
@@ -29,13 +28,25 @@
 	userId := "1"
 	repo := "group/repo"
 
+	env := sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: "git-upload-pack " + repo,
+		RemoteAddr:      "127.0.0.1",
+	}
+
+	args := &commandargs.Shell{
+		GitlabKeyId: userId,
+		CommandType: commandargs.UploadPack,
+		SshArgs:     []string{"git-upload-pack", repo},
+		Env:         env,
+	}
+
 	cmd := &Command{
 		Config:     &config.Config{GitlabUrl: url},
-		Args:       &commandargs.Shell{GitlabKeyId: userId, CommandType: commandargs.UploadPack, SshArgs: []string{"git-upload-pack", repo}},
+		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
 
-	hook := testhelper.SetupLogger()
 	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
@@ -43,25 +54,20 @@
 	require.NoError(t, err)
 
 	require.Equal(t, "UploadPack: "+repo, output.String())
-	require.Eventually(t, func() bool {
-		entries := hook.AllEntries()
-
-		require.Equal(t, 2, len(entries))
-		require.Contains(t, entries[1].Message, "executing git command")
-		require.Contains(t, entries[1].Message, "command=git-upload-pack")
-		require.Contains(t, entries[1].Message, "gl_key_type=key")
-		require.Contains(t, entries[1].Message, "gl_key_id=123")
-		require.Contains(t, entries[1].Message, "correlation_id=a-correlation-id")
-		return true
-	}, time.Second, time.Millisecond)
 
 	for k, v := range map[string]string{
 		"gitaly-feature-cache_invalidator":        "true",
 		"gitaly-feature-inforef_uploadpack_cache": "false",
+		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-pack",
+		"key_id":                                  "123",
+		"user_id":                                 "1",
+		"remote_ip":                               "127.0.0.1",
+		"key_type":                                "key",
 	} {
 		actual := testServer.ReceivedMD[k]
 		require.Len(t, actual, 1)
 		require.Equal(t, v, actual[0])
 	}
 	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 }
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -9,11 +9,13 @@
 	"path/filepath"
 	"strings"
 	"sync"
+	"time"
 
-	"gitlab.com/gitlab-org/labkit/tracing"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
 	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
 
 const (
@@ -22,8 +24,8 @@
 )
 
 var (
-       defaultHgBinPath string = "hg"
-       defaultHgrcPath  string = "/opt/gitlab/etc/docker.hgrc:/etc/gitlab/heptapod.hgrc"
+	defaultHgBinPath string = "hg"
+	defaultHgrcPath  string = "/opt/gitlab/etc/docker.hgrc:/etc/gitlab/heptapod.hgrc"
 )
 
 type ServerConfig struct {
@@ -31,6 +33,9 @@
 	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
 	WebListen               string   `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
+	GracePeriodSeconds      uint64   `yaml:"grace_period"`
+	ReadinessProbe          string   `yaml:"readiness_probe"`
+	LivenessProbe           string   `yaml:"liveness_probe"`
 	HostKeyFiles            []string `yaml:"host_key_files,omitempty"`
 }
 
@@ -76,10 +81,11 @@
 	Secret         string             `yaml:"secret"`
 	SslCertDir     string             `yaml:"ssl_cert_dir"`
 	HttpSettings   HttpSettingsConfig `yaml:"http_settings"`
-	Mercurial             MercurialConfig    `yaml:"mercurial"`
+	Mercurial      MercurialConfig    `yaml:"mercurial"`
 	Server         ServerConfig       `yaml:"sshd"`
 
 	httpClient     *client.HttpClient
+	httpClientErr  error
 	httpClientOnce sync.Once
 }
 
@@ -87,7 +93,7 @@
 var (
 	DefaultConfig = Config{
 		LogFile:   "gitlab-shell.log",
-		LogFormat: "text",
+		LogFormat: "json",
 		Server:    DefaultServerConfig,
 		User:      "git",
 	}
@@ -96,6 +102,9 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
+		GracePeriodSeconds:      10,
+		ReadinessProbe:          "/start",
+		LivenessProbe:           "/health",
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
@@ -104,30 +113,39 @@
 	}
 )
 
+func (sc *ServerConfig) GracePeriod() time.Duration {
+	return time.Duration(sc.GracePeriodSeconds) * time.Second
+}
+
 func (c *Config) ApplyGlobalState() {
 	if c.SslCertDir != "" {
 		os.Setenv("SSL_CERT_DIR", c.SslCertDir)
 	}
 }
 
-func (c *Config) HttpClient() *client.HttpClient {
+func (c *Config) HttpClient() (*client.HttpClient, error) {
 	c.httpClientOnce.Do(func() {
-		client := client.NewHTTPClient(
+		client, err := client.NewHTTPClientWithOpts(
 			c.GitlabUrl,
 			c.GitlabRelativeURLRoot,
 			c.HttpSettings.CaFile,
 			c.HttpSettings.CaPath,
 			c.HttpSettings.SelfSignedCert,
 			c.HttpSettings.ReadTimeoutSeconds,
+			nil,
 		)
+		if err != nil {
+			c.httpClientErr = err
+			return
+		}
 
 		tr := client.Transport
-		client.Transport = tracing.NewRoundTripper(tr)
+		client.Transport = promhttp.InstrumentRoundTripperDuration(metrics.HttpRequestDuration, tr)
 
 		c.httpClient = client
 	})
 
-	return c.httpClient
+	return c.httpClient, c.httpClientErr
 }
 
 // NewFromDirExternal returns a new config from a given root dir. It also applies defaults appropriate for
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -4,8 +4,10 @@
 	"os"
 	"testing"
 
+	"github.com/prometheus/client_golang/prometheus"
 	"github.com/stretchr/testify/require"
 
+	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
 
@@ -22,3 +24,28 @@
 
 	require.Equal(t, "foo", os.Getenv("SSL_CERT_DIR"))
 }
+
+func TestHttpClient(t *testing.T) {
+	url := testserver.StartHttpServer(t, []testserver.TestRequestHandler{})
+
+	config := &Config{GitlabUrl: url}
+	client, err := config.HttpClient()
+	require.NoError(t, err)
+
+	_, err = client.Get("http://host.com/path")
+	require.NoError(t, err)
+
+	ms, err := prometheus.DefaultGatherer.Gather()
+	require.NoError(t, err)
+
+	lastMetric := ms[0]
+	require.Equal(t, lastMetric.GetName(), "gitlab_shell_http_request_seconds")
+
+	labels := lastMetric.GetMetric()[0].Label
+
+	require.Equal(t, "code", labels[0].GetName())
+	require.Equal(t, "404", labels[0].GetValue())
+
+	require.Equal(t, "method", labels[1].GetName())
+	require.Equal(t, "get", labels[1].GetValue())
+}
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -163,9 +163,7 @@
 }
 
 func setup(t *testing.T, allowedPayload string) *Client {
-	testDirCleanup, err := testhelper.PrepareTestRootDir()
-	require.NoError(t, err)
-	defer testDirCleanup()
+	testhelper.PrepareTestRootDir(t)
 
 	body, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
 	require.NoError(t, err)
diff --git a/internal/gitlabnet/accessverifier/hg_client_test.go b/internal/gitlabnet/accessverifier/hg_client_test.go
--- a/internal/gitlabnet/accessverifier/hg_client_test.go
+++ b/internal/gitlabnet/accessverifier/hg_client_test.go
@@ -111,9 +111,7 @@
 }
 
 func hgSetup(t *testing.T) *HgClient {
-	testDirCleanup, err := testhelper.PrepareTestRootDir()
-	require.NoError(t, err)
-	defer testDirCleanup()
+	testhelper.PrepareTestRootDir(t)
 
 	body, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "responses/hg_allowed.json"))
 	require.NoError(t, err)
diff --git a/internal/gitlabnet/client.go b/internal/gitlabnet/client.go
--- a/internal/gitlabnet/client.go
+++ b/internal/gitlabnet/client.go
@@ -15,7 +15,10 @@
 )
 
 func GetClient(config *config.Config) (*client.GitlabNetClient, error) {
-	httpClient := config.HttpClient()
+	httpClient, err := config.HttpClient()
+	if err != nil {
+		return nil, err
+	}
 
 	if httpClient == nil {
 		return nil, fmt.Errorf("Unsupported protocol")
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -8,18 +8,19 @@
 
 	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
 	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
-	log "github.com/sirupsen/logrus"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/metadata"
 
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+
 	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
+	"gitlab.com/gitlab-org/labkit/log"
 	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
 )
 
@@ -75,7 +76,6 @@
 func (gc *GitalyCommand) LogExecution(ctx context.Context, repository *pb.Repository, response *accessverifier.Response, env sshenv.Env) {
 	fields := log.Fields{
 		"command":         gc.ServiceName,
-		"correlation_id":  correlation.ExtractFromContext(ctx),
 		"gl_project_path": repository.GlProjectPath,
 		"gl_repository":   repository.GlRepository,
 		"user_id":         response.UserId,
@@ -86,7 +86,7 @@
 		"gl_key_id":       response.KeyId,
 	}
 
-	log.WithFields(fields).Info("executing git command")
+	log.WithContextFields(ctx, fields).Info("executing git command")
 }
 
 func withOutgoingMetadata(ctx context.Context, features map[string]string) context.Context {
@@ -108,9 +108,9 @@
 
 	serviceName := correlation.ExtractClientNameFromContext(ctx)
 	if serviceName == "" {
-		log.Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
+		serviceName = "gitlab-shell-unknown"
 
-		serviceName = "gitlab-shell-unknown"
+		log.WithFields(log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
 	}
 
 	serviceName = fmt.Sprintf("%s-%s", serviceName, gc.ServiceName)
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -2,25 +2,54 @@
 
 import (
 	"fmt"
+	"io"
 	"io/ioutil"
 	"log/syslog"
 	"os"
+	"time"
 
-	log "github.com/sirupsen/logrus"
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
-func configureLogFormat(cfg *config.Config) {
-	if cfg.LogFormat == "json" {
-		log.SetFormatter(&log.JSONFormatter{})
+func logFmt(inFmt string) string {
+	// Hide the "combined" format, since that makes no sense in gitlab-shell.
+	// The default is JSON when unspecified.
+	if inFmt == "" || inFmt == "combined" {
+		return "json"
+	}
+
+	return inFmt
+}
+
+func logFile(inFile string) string {
+	if inFile == "" {
+		return "stderr"
+	}
+
+	return inFile
+}
+
+func buildOpts(cfg *config.Config) []log.LoggerOption {
+	return []log.LoggerOption{
+		log.WithFormatter(logFmt(cfg.LogFormat)),
+		log.WithOutputName(logFile(cfg.LogFile)),
+		log.WithTimezone(time.UTC),
 	}
 }
 
 // Configure configures the logging singleton for operation inside a remote TTY (like SSH). In this
 // mode an empty LogFile is not accepted and syslog is used as a fallback when LogFile could not be
 // opened for writing.
-func Configure(cfg *config.Config) {
-	logFile, err := os.OpenFile(cfg.LogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
+func Configure(cfg *config.Config) io.Closer {
+	var closer io.Closer = ioutil.NopCloser(nil)
+	err := fmt.Errorf("No logfile specified")
+
+	if cfg.LogFile != "" {
+		closer, err = log.Initialize(buildOpts(cfg)...)
+	}
+
 	if err != nil {
 		progName, _ := os.Executable()
 		syslogLogger, syslogLoggerErr := syslog.NewLogger(syslog.LOG_ERR|syslog.LOG_USER, 0)
@@ -32,29 +61,35 @@
 			fmt.Fprintf(os.Stderr, msg)
 		}
 
-		// Discard logs since a log file was specified but couldn't be opened
-		log.SetOutput(ioutil.Discard)
+		cfg.LogFile = "/dev/null"
+		closer, err = log.Initialize(buildOpts(cfg)...)
+		if err != nil {
+			log.WithError(err).Warn("Unable to configure logging to /dev/null, leaving unconfigured")
+		}
 	}
 
-	log.SetOutput(logFile)
-
-	configureLogFormat(cfg)
+	return closer
 }
 
 // ConfigureStandalone configures the logging singleton for standalone operation. In this mode an
-// empty LogFile is treated as logging to standard output and standard output is used as a fallback
+// empty LogFile is treated as logging to stderr, and standard output is used as a fallback
 // when LogFile could not be opened for writing.
-func ConfigureStandalone(cfg *config.Config) {
-	if cfg.LogFile == "" {
-		return
+func ConfigureStandalone(cfg *config.Config) io.Closer {
+	closer, err1 := log.Initialize(buildOpts(cfg)...)
+	if err1 != nil {
+		var err2 error
+
+		cfg.LogFile = "stdout"
+		closer, err2 = log.Initialize(buildOpts(cfg)...)
+
+		// Output this after the logger has been configured!
+		log.WithError(err1).WithField("log_file", cfg.LogFile).Warn("Unable to configure logging, falling back to STDOUT")
+
+		// LabKit v1.7.0 doesn't have any conditions where logging to "stdout" will fail
+		if err2 != nil {
+			log.WithError(err2).Warn("Unable to configure logging to STDOUT, leaving unconfigured")
+		}
 	}
 
-	logFile, err := os.OpenFile(cfg.LogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
-	if err != nil {
-		log.Printf("Unable to configure logging, falling back to stdout: %v", err)
-		return
-	}
-	log.SetOutput(logFile)
-
-	configureLogFormat(cfg)
+	return closer
 }
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -3,11 +3,13 @@
 import (
 	"io/ioutil"
 	"os"
+	"regexp"
 	"strings"
 	"testing"
 
-	log "github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
@@ -21,7 +23,9 @@
 		LogFormat: "json",
 	}
 
-	Configure(&config)
+	closer := Configure(&config)
+	defer closer.Close()
+
 	log.Info("this is a test")
 
 	tmpFile.Close()
@@ -41,6 +45,34 @@
 		LogFormat: "json",
 	}
 
-	Configure(&config)
+	closer := Configure(&config)
+	defer closer.Close()
+
 	log.Info("this is a test")
 }
+
+func TestLogInUTC(t *testing.T) {
+	tmpFile, err := ioutil.TempFile(os.TempDir(), "logtest-")
+	require.NoError(t, err)
+	defer tmpFile.Close()
+	defer os.Remove(tmpFile.Name())
+
+	config := config.Config{
+		LogFile:   tmpFile.Name(),
+		LogFormat: "json",
+	}
+
+	closer := Configure(&config)
+	defer closer.Close()
+
+	log.Info("this is a test")
+
+	data, err := ioutil.ReadFile(tmpFile.Name())
+	require.NoError(t, err)
+
+	utc := `[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z`
+	r, e := regexp.MatchString(utc, string(data))
+
+	require.NoError(t, e)
+	require.True(t, r)
+}
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
new file mode 100644
--- /dev/null
+++ b/internal/metrics/metrics.go
@@ -0,0 +1,63 @@
+package metrics
+
+import (
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promauto"
+)
+
+const (
+	namespace     = "gitlab_shell"
+	sshdSubsystem = "sshd"
+	httpSubsystem = "http"
+)
+
+var (
+	SshdConnectionDuration = promauto.NewHistogram(
+		prometheus.HistogramOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      "connection_duration_seconds",
+			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
+			Buckets: []float64{
+				0.005, /* 5ms */
+				0.025, /* 25ms */
+				0.1,   /* 100ms */
+				0.5,   /* 500ms */
+				1.0,   /* 1s */
+				10.0,  /* 10s */
+				30.0,  /* 30s */
+				60.0,  /* 1m */
+				300.0, /* 5m */
+			},
+		},
+	)
+
+	SshdHitMaxSessions = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      "concurrent_limited_sessions_total",
+			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
+		},
+	)
+
+	HttpRequestDuration = promauto.NewHistogramVec(
+		prometheus.HistogramOpts{
+			Namespace: namespace,
+			Subsystem: httpSubsystem,
+			Name:      "request_seconds",
+			Help:      "A histogram of latencies for gitlab-shell http requests",
+			Buckets: []float64{
+				0.01, /* 10ms */
+				0.05, /* 50ms */
+				0.1,  /* 100ms */
+				0.25, /* 250ms */
+				0.5,  /* 500ms */
+				1.0,  /* 1s */
+				5.0,  /* 5s */
+				10.0, /* 10s */
+			},
+		},
+		[]string{"code", "method"},
+	)
+)
diff --git a/internal/pktline/pktline.go b/internal/pktline/pktline.go
--- a/internal/pktline/pktline.go
+++ b/internal/pktline/pktline.go
@@ -8,6 +8,7 @@
 	"bytes"
 	"fmt"
 	"io"
+	"regexp"
 	"strconv"
 )
 
@@ -16,6 +17,8 @@
 	pktDelim   = "0001"
 )
 
+var branchRemovalPktRegexp = regexp.MustCompile(`\A[a-f0-9]{4}[a-f0-9]{40} 0{40} `)
+
 // NewScanner returns a bufio.Scanner that splits on Git pktline boundaries
 func NewScanner(r io.Reader) *bufio.Scanner {
 	scanner := bufio.NewScanner(r)
@@ -24,7 +27,16 @@
 	return scanner
 }
 
-// IsDone detects the special flush packet '0009done\n'
+func IsRefRemoval(pkt []byte) bool {
+	return branchRemovalPktRegexp.Match(pkt)
+}
+
+// IsFlush detects the special flush packet '0000'
+func IsFlush(pkt []byte) bool {
+	return bytes.Equal(pkt, []byte("0000"))
+}
+
+// IsDone detects the special done packet '0009done\n'
 func IsDone(pkt []byte) bool {
 	return bytes.Equal(pkt, PktDone())
 }
diff --git a/internal/pktline/pktline_test.go b/internal/pktline/pktline_test.go
--- a/internal/pktline/pktline_test.go
+++ b/internal/pktline/pktline_test.go
@@ -68,6 +68,40 @@
 	}
 }
 
+func TestIsRefRemoval(t *testing.T) {
+	testCases := []struct {
+		in        string
+		isRemoval bool
+	}{
+		{in: "003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug", isRemoval: false},
+		{in: "003f0000000000000000000000000000000000000000 7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug", isRemoval: false},
+		{in: "003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 0000000000000000000000000000000000000000 refs/heads/debug", isRemoval: true},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.in, func(t *testing.T) {
+			require.Equal(t, tc.isRemoval, IsRefRemoval([]byte(tc.in)))
+		})
+	}
+}
+
+func TestIsFlush(t *testing.T) {
+	testCases := []struct {
+		in    string
+		flush bool
+	}{
+		{in: "0008abcd", flush: false},
+		{in: "invalid packet", flush: false},
+		{in: "0000", flush: true},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.in, func(t *testing.T) {
+			require.Equal(t, tc.flush, IsFlush([]byte(tc.in)))
+		})
+	}
+}
+
 func TestIsDone(t *testing.T) {
 	testCases := []struct {
 		in   string
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -4,47 +4,12 @@
 	"context"
 	"time"
 
-	"github.com/prometheus/client_golang/prometheus"
-	"github.com/prometheus/client_golang/prometheus/promauto"
-	log "github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
-)
-
-const (
-	namespace     = "gitlab_shell"
-	sshdSubsystem = "sshd"
-)
 
-var (
-	sshdConnectionDuration = promauto.NewHistogram(
-		prometheus.HistogramOpts{
-			Namespace: namespace,
-			Subsystem: sshdSubsystem,
-			Name:      "connection_duration_seconds",
-			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
-			Buckets: []float64{
-				0.005, /* 5ms */
-				0.025, /* 25ms */
-				0.1,   /* 100ms */
-				0.5,   /* 500ms */
-				1.0,   /* 1s */
-				10.0,  /* 10s */
-				30.0,  /* 30s */
-				60.0,  /* 1m */
-				300.0, /* 5m */
-			},
-		},
-	)
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
-	sshdHitMaxSessions = promauto.NewCounter(
-		prometheus.CounterOpts{
-			Namespace: namespace,
-			Subsystem: sshdSubsystem,
-			Name:      "concurrent_limited_sessions_total",
-			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
-		},
-	)
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 type connection struct {
@@ -64,7 +29,7 @@
 }
 
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
-	defer sshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
+	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
 
 	for newChannel := range chans {
 		if newChannel.ChannelType() != "session" {
@@ -73,12 +38,12 @@
 		}
 		if !c.concurrentSessions.TryAcquire(1) {
 			newChannel.Reject(ssh.ResourceShortage, "too many concurrent sessions")
-			sshdHitMaxSessions.Inc()
+			metrics.SshdHitMaxSessions.Inc()
 			continue
 		}
 		channel, requests, err := newChannel.Accept()
 		if err != nil {
-			log.Infof("Could not accept channel: %v", err)
+			log.WithError(err).Info("could not accept channel")
 			c.concurrentSessions.Release(1)
 			continue
 		}
@@ -89,7 +54,7 @@
 			// Prevent a panic in a single session from taking out the whole server
 			defer func() {
 				if err := recover(); err != nil {
-					log.Warnf("panic handling session from %s: recovered: %#+v", c.remoteAddr, err)
+					log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", c.remoteAddr)
 				}
 			}()
 
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -2,6 +2,7 @@
 
 import (
 	"context"
+	"errors"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -9,22 +10,31 @@
 )
 
 type rejectCall struct {
-	reason ssh.RejectionReason
+	reason  ssh.RejectionReason
 	message string
 }
 
 type fakeNewChannel struct {
 	channelType string
 	extraData   []byte
+	acceptErr   error
+
+	acceptCh chan struct{}
 	rejectCh chan rejectCall
 }
 
 func (f *fakeNewChannel) Accept() (ssh.Channel, <-chan *ssh.Request, error) {
-	return nil, nil, nil
+	if f.acceptCh != nil {
+		f.acceptCh <- struct{}{}
+	}
+
+	return nil, nil, f.acceptErr
 }
 
 func (f *fakeNewChannel) Reject(reason ssh.RejectionReason, message string) error {
-	f.rejectCh <- rejectCall{reason: reason, message: message}
+	if f.rejectCh != nil {
+		f.rejectCh <- rejectCall{reason: reason, message: message}
+	}
 
 	return nil
 }
@@ -63,7 +73,9 @@
 }
 
 func TestUnknownChannelType(t *testing.T) {
-	rejectCh := make(chan rejectCall, 1)
+	rejectCh := make(chan rejectCall)
+	defer close(rejectCh)
+
 	newChannel := &fakeNewChannel{channelType: "unknown session", rejectCh: rejectCh}
 	conn, chans := setup(1, newChannel)
 
@@ -72,8 +84,64 @@
 	}()
 
 	rejectionData := <-rejectCh
-	close(rejectCh)
 
 	expectedRejection := rejectCall{reason: ssh.UnknownChannelType, message: "unknown channel type"}
 	require.Equal(t, expectedRejection, rejectionData)
 }
+
+func TestTooManySessions(t *testing.T) {
+	rejectCh := make(chan rejectCall)
+	defer close(rejectCh)
+
+	newChannel := &fakeNewChannel{channelType: "session", rejectCh: rejectCh}
+	conn, chans := setup(1, newChannel)
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	go func() {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+			<-ctx.Done() // Keep the accepted channel open until the end of the test
+		})
+	}()
+
+	chans <- newChannel
+	require.Equal(t, <-rejectCh, rejectCall{reason: ssh.ResourceShortage, message: "too many concurrent sessions"})
+}
+
+func TestAcceptSessionSucceeds(t *testing.T) {
+	newChannel := &fakeNewChannel{channelType: "session"}
+	conn, chans := setup(1, newChannel)
+
+	channelHandled := false
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		channelHandled = true
+		close(chans)
+	})
+
+	require.True(t, channelHandled)
+}
+
+func TestAcceptSessionFails(t *testing.T) {
+	acceptCh := make(chan struct{})
+	defer close(acceptCh)
+
+	acceptErr := errors.New("some failure")
+	newChannel := &fakeNewChannel{channelType: "session", acceptCh: acceptCh, acceptErr: acceptErr}
+	conn, chans := setup(1, newChannel)
+
+	channelHandled := false
+	go func() {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+			channelHandled = true
+		})
+	}()
+
+	require.Equal(t, <-acceptCh, struct{}{})
+
+	// Waits until the number of sessions is back to 0, since we can only have 1
+	conn.concurrentSessions.Acquire(context.Background(), 1)
+	defer conn.concurrentSessions.Release(1)
+
+	require.False(t, channelHandled)
+}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -7,41 +7,171 @@
 	"fmt"
 	"io/ioutil"
 	"net"
+	"net/http"
 	"strconv"
+	"sync"
 	"time"
 
-	log "github.com/sirupsen/logrus"
-
 	"github.com/pires/go-proxyproto"
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
+
 	"gitlab.com/gitlab-org/labkit/correlation"
+	"gitlab.com/gitlab-org/labkit/log"
+)
+
+type status int
+
+const (
+	StatusStarting status = iota
+	StatusReady
+	StatusOnShutdown
+	StatusClosed
+	ProxyHeaderTimeout = 90 * time.Second
 )
 
-func Run(ctx context.Context, cfg *config.Config) error {
+type Server struct {
+	Config *config.Config
+
+	status               status
+	statusMu             sync.Mutex
+	wg                   sync.WaitGroup
+	listener             net.Listener
+	hostKeys             []ssh.Signer
+	authorizedKeysClient *authorizedkeys.Client
+}
+
+func NewServer(cfg *config.Config) (*Server, error) {
 	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
 	if err != nil {
-		return fmt.Errorf("failed to initialize GitLab client: %w", err)
+		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+	}
+
+	var hostKeys []ssh.Signer
+	for _, filename := range cfg.Server.HostKeyFiles {
+		keyRaw, err := ioutil.ReadFile(filename)
+		if err != nil {
+			log.WithError(err).Warnf("Failed to read host key %v", filename)
+			continue
+		}
+		key, err := ssh.ParsePrivateKey(keyRaw)
+		if err != nil {
+			log.WithError(err).Warnf("Failed to parse host key %v", filename)
+			continue
+		}
+
+		hostKeys = append(hostKeys, key)
+	}
+	if len(hostKeys) == 0 {
+		return nil, fmt.Errorf("No host keys could be loaded, aborting")
 	}
 
-	sshListener, err := net.Listen("tcp", cfg.Server.Listen)
+	return &Server{Config: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
+}
+
+func (s *Server) ListenAndServe(ctx context.Context) error {
+	if err := s.listen(); err != nil {
+		return err
+	}
+	defer s.listener.Close()
+
+	s.serve(ctx)
+
+	return nil
+}
+
+func (s *Server) Shutdown() error {
+	if s.listener == nil {
+		return nil
+	}
+
+	s.changeStatus(StatusOnShutdown)
+
+	return s.listener.Close()
+}
+
+func (s *Server) MonitoringServeMux() *http.ServeMux {
+	mux := http.NewServeMux()
+
+	mux.HandleFunc(s.Config.Server.ReadinessProbe, func(w http.ResponseWriter, r *http.Request) {
+		if s.getStatus() == StatusReady {
+			w.WriteHeader(http.StatusOK)
+		} else {
+			w.WriteHeader(http.StatusServiceUnavailable)
+		}
+	})
+
+	mux.HandleFunc(s.Config.Server.LivenessProbe, func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusOK)
+	})
+
+	return mux
+}
+
+func (s *Server) listen() error {
+	sshListener, err := net.Listen("tcp", s.Config.Server.Listen)
 	if err != nil {
 		return fmt.Errorf("failed to listen for connection: %w", err)
 	}
-	if cfg.Server.ProxyProtocol {
-		sshListener = &proxyproto.Listener{Listener: sshListener}
+
+	if s.Config.Server.ProxyProtocol {
+		sshListener = &proxyproto.Listener{
+			Listener:          sshListener,
+			ReadHeaderTimeout: ProxyHeaderTimeout,
+		}
 
 		log.Info("Proxy protocol is enabled")
 	}
-	defer sshListener.Close()
+
+	log.WithFields(log.Fields{"tcp_address": sshListener.Addr().String()}).Info("Listening for SSH connections")
+
+	s.listener = sshListener
+
+	return nil
+}
+
+func (s *Server) serve(ctx context.Context) {
+	s.changeStatus(StatusReady)
+
+	for {
+		nconn, err := s.listener.Accept()
+		if err != nil {
+			if s.getStatus() == StatusOnShutdown {
+				break
+			}
+
+			log.WithError(err).Warn("Failed to accept connection")
+			continue
+		}
 
-	log.Infof("Listening on %v", sshListener.Addr().String())
+		s.wg.Add(1)
+		go s.handleConn(ctx, nconn)
+	}
+
+	s.wg.Wait()
+
+	s.changeStatus(StatusClosed)
+}
 
+func (s *Server) changeStatus(st status) {
+	s.statusMu.Lock()
+	s.status = st
+	s.statusMu.Unlock()
+}
+
+func (s *Server) getStatus() status {
+	s.statusMu.Lock()
+	defer s.statusMu.Unlock()
+
+	return s.status
+}
+
+func (s *Server) serverConfig(ctx context.Context) *ssh.ServerConfig {
 	sshCfg := &ssh.ServerConfig{
 		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
-			if conn.User() != cfg.User {
+			if conn.User() != s.Config.User {
 				return nil, errors.New("unknown user")
 			}
 			if key.Type() == ssh.KeyAlgoDSA {
@@ -49,7 +179,7 @@
 			}
 			ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
 			defer cancel()
-			res, err := authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
+			res, err := s.authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
 			if err != nil {
 				return nil, err
 			}
@@ -63,63 +193,41 @@
 		},
 	}
 
-	var loadedHostKeys uint
-	for _, filename := range cfg.Server.HostKeyFiles {
-		keyRaw, err := ioutil.ReadFile(filename)
-		if err != nil {
-			log.Warnf("Failed to read host key %v: %v", filename, err)
-			continue
-		}
-		key, err := ssh.ParsePrivateKey(keyRaw)
-		if err != nil {
-			log.Warnf("Failed to parse host key %v: %v", filename, err)
-			continue
-		}
-		loadedHostKeys++
+	for _, key := range s.hostKeys {
 		sshCfg.AddHostKey(key)
 	}
-	if loadedHostKeys == 0 {
-		return fmt.Errorf("No host keys could be loaded, aborting")
-	}
 
-	for {
-		nconn, err := sshListener.Accept()
-		if err != nil {
-			log.Warnf("Failed to accept connection: %v\n", err)
-			continue
-		}
-
-		go handleConn(ctx, cfg, sshCfg, nconn)
-	}
+	return sshCfg
 }
 
-func handleConn(ctx context.Context, cfg *config.Config, sshCfg *ssh.ServerConfig, nconn net.Conn) {
+func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
 	remoteAddr := nconn.RemoteAddr().String()
 
+	defer s.wg.Done()
 	defer nconn.Close()
 
 	// Prevent a panic in a single connection from taking out the whole server
 	defer func() {
 		if err := recover(); err != nil {
-			log.Warnf("panic handling connection from %s: recovered: %#+v", remoteAddr, err)
+			log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", remoteAddr)
 		}
 	}()
 
 	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
 	defer cancel()
 
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, sshCfg)
+	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig(ctx))
 	if err != nil {
-		log.Infof("Failed to initialize SSH connection: %v", err)
+		log.WithError(err).Info("Failed to initialize SSH connection")
 		return
 	}
 
 	go ssh.DiscardRequests(reqs)
 
-	conn := newConnection(cfg.Server.ConcurrentSessionsLimit, remoteAddr)
+	conn := newConnection(s.Config.Server.ConcurrentSessionsLimit, remoteAddr)
 	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
 		session := &session{
-			cfg:         cfg,
+			cfg:         s.Config,
 			channel:     channel,
 			gitlabKeyId: sconn.Permissions.Extensions["key-id"],
 			remoteAddr:  remoteAddr,
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/sshd_test.go
@@ -0,0 +1,190 @@
+package sshd
+
+import (
+	"context"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"path"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+)
+
+const (
+	serverUrl = "127.0.0.1:50000"
+	user      = "git"
+)
+
+var (
+	correlationId = ""
+)
+
+func TestListenAndServe(t *testing.T) {
+	s := setupServer(t)
+
+	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.NoError(t, err)
+	defer client.Close()
+
+	require.NoError(t, s.Shutdown())
+	verifyStatus(t, s, StatusOnShutdown)
+
+	holdSession(t, client)
+
+	_, err = ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.Equal(t, err.Error(), "dial tcp 127.0.0.1:50000: connect: connection refused")
+
+	client.Close()
+
+	verifyStatus(t, s, StatusClosed)
+}
+
+func TestCorrelationId(t *testing.T) {
+	setupServer(t)
+
+	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.NoError(t, err)
+	defer client.Close()
+
+	holdSession(t, client)
+
+	previousCorrelationId := correlationId
+
+	client, err = ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.NoError(t, err)
+	defer client.Close()
+
+	holdSession(t, client)
+
+	require.NotEqual(t, previousCorrelationId, correlationId)
+}
+
+func TestReadinessProbe(t *testing.T) {
+	s := &Server{Config: &config.Config{Server: config.DefaultServerConfig}}
+
+	require.Equal(t, StatusStarting, s.getStatus())
+
+	mux := s.MonitoringServeMux()
+
+	req := httptest.NewRequest("GET", "/start", nil)
+
+	r := httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 503, r.Result().StatusCode)
+
+	s.changeStatus(StatusReady)
+
+	r = httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 200, r.Result().StatusCode)
+
+	s.changeStatus(StatusOnShutdown)
+
+	r = httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 503, r.Result().StatusCode)
+}
+
+func TestLivenessProbe(t *testing.T) {
+	s := &Server{Config: &config.Config{Server: config.DefaultServerConfig}}
+	mux := s.MonitoringServeMux()
+
+	req := httptest.NewRequest("GET", "/health", nil)
+
+	r := httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 200, r.Result().StatusCode)
+}
+
+func setupServer(t *testing.T) *Server {
+	t.Helper()
+
+	requests := []testserver.TestRequestHandler{
+		{
+			Path: "/api/v4/internal/authorized_keys",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				correlationId = r.Header.Get("X-Request-Id")
+
+				require.NotEmpty(t, correlationId)
+
+				fmt.Fprint(w, `{"id": 1000, "key": "key"}`)
+			},
+		}, {
+			Path: "/api/v4/internal/discover",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				require.Equal(t, correlationId, r.Header.Get("X-Request-Id"))
+
+				fmt.Fprint(w, `{"id": 1000, "name": "Test User", "username": "test-user"}`)
+			},
+		},
+	}
+
+	testhelper.PrepareTestRootDir(t)
+
+	url := testserver.StartSocketHttpServer(t, requests)
+	srvCfg := config.ServerConfig{
+		Listen:                  serverUrl,
+		ConcurrentSessionsLimit: 1,
+		HostKeyFiles:            []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")},
+	}
+
+	s, err := NewServer(&config.Config{User: user, RootDir: "/tmp", GitlabUrl: url, Server: srvCfg})
+	require.NoError(t, err)
+
+	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
+	t.Cleanup(func() { s.Shutdown() })
+
+	verifyStatus(t, s, StatusReady)
+
+	return s
+}
+
+func clientConfig(t *testing.T) *ssh.ClientConfig {
+	keyRaw, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server_authorized_key"))
+	pKey, _, _, _, err := ssh.ParseAuthorizedKey(keyRaw)
+	require.NoError(t, err)
+
+	key, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/client/key.pem"))
+	require.NoError(t, err)
+	signer, err := ssh.ParsePrivateKey(key)
+	require.NoError(t, err)
+
+	return &ssh.ClientConfig{
+		User: user,
+		Auth: []ssh.AuthMethod{
+			ssh.PublicKeys(signer),
+		},
+		HostKeyCallback: ssh.FixedHostKey(pKey),
+	}
+}
+
+func holdSession(t *testing.T, c *ssh.Client) {
+	session, err := c.NewSession()
+	require.NoError(t, err)
+	defer session.Close()
+
+	output, err := session.Output("discover")
+	require.NoError(t, err)
+	require.Equal(t, "Welcome to GitLab, @test-user!\n", string(output))
+}
+
+func verifyStatus(t *testing.T, s *Server, st status) {
+	for i := 5; i < 500; i += 50 {
+		if s.getStatus() == st {
+			break
+		}
+
+		// Sleep incrementally ~2s in total
+		time.Sleep(time.Duration(i) * time.Millisecond)
+	}
+
+	require.Equal(t, st, s.getStatus())
+}
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -65,10 +65,10 @@
 					"gl_email":        "testgr@heptapod.test",
 					"gl_id":           17,
 				}
-				if (hg_native != nil) {
+				if hg_native != nil {
 					body["hg_native"] = *hg_native
 				}
-				if (no_git != nil) {
+				if no_git != nil {
 					body["no_git"] = *no_git
 				}
 				w.WriteHeader(http.StatusOK)
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server_authorized_key b/internal/testhelper/testdata/testroot/certs/valid/server_authorized_key
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server_authorized_key
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yql
diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go
--- a/internal/testhelper/testhelper.go
+++ b/internal/testhelper/testhelper.go
@@ -6,11 +6,10 @@
 	"os"
 	"path"
 	"runtime"
-	"time"
+	"testing"
 
 	"github.com/otiai10/copy"
-	"github.com/sirupsen/logrus"
-	"github.com/sirupsen/logrus/hooks/test"
+	"github.com/stretchr/testify/require"
 )
 
 var (
@@ -31,42 +30,21 @@
 	}
 }
 
-func PrepareTestRootDir() (func(), error) {
-	if err := os.MkdirAll(TestRoot, 0700); err != nil {
-		return nil, err
-	}
+func PrepareTestRootDir(t *testing.T) {
+	t.Helper()
 
-	var oldWd string
-	cleanup := func() {
-		if oldWd != "" {
-			err := os.Chdir(oldWd)
-			if err != nil {
-				panic(err)
-			}
-		}
+	require.NoError(t, os.MkdirAll(TestRoot, 0700))
 
-		if err := os.RemoveAll(TestRoot); err != nil {
-			panic(err)
-		}
-	}
+	t.Cleanup(func() { os.RemoveAll(TestRoot) })
 
-	if err := copyTestData(); err != nil {
-		cleanup()
-		return nil, err
-	}
+	require.NoError(t, copyTestData())
 
 	oldWd, err := os.Getwd()
-	if err != nil {
-		cleanup()
-		return nil, err
-	}
+	require.NoError(t, err)
 
-	if err := os.Chdir(TestRoot); err != nil {
-		cleanup()
-		return nil, err
-	}
+	t.Cleanup(func() { os.Chdir(oldWd) })
 
-	return cleanup, nil
+	require.NoError(t, os.Chdir(TestRoot))
 }
 
 func copyTestData() error {
@@ -94,25 +72,3 @@
 	err := os.Setenv(key, value)
 	return func() { os.Setenv(key, oldValue) }, err
 }
-
-func SetupLogger() *test.Hook {
-	logger, hook := test.NewNullLogger()
-	logrus.SetOutput(logger.Writer())
-
-	return hook
-}
-
-// logrus fires a Goroutine to write the output log, but there's no way to
-// flush all outstanding hooks to fire. We just wait up to a second
-// for an event to appear.
-func WaitForLogEvent(hook *test.Hook) bool {
-	for i := 0; i < 10; i++ {
-		if entry := hook.LastEntry(); entry != nil {
-			return true
-		}
-
-		time.Sleep(100 * time.Millisecond)
-	}
-
-	return false
-}
diff --git a/spec/gitlab_shell_custom_git_receive_pack_spec.rb b/spec/gitlab_shell_custom_git_receive_pack_spec.rb
--- a/spec/gitlab_shell_custom_git_receive_pack_spec.rb
+++ b/spec/gitlab_shell_custom_git_receive_pack_spec.rb
@@ -74,11 +74,11 @@
           expect(stderr.gets).to eq("remote: message\n")
           expect(stderr.gets).to eq(remote_blank_line)
 
-          stdin.puts("input")
+          stdin.puts("0009input")
           stdin.close
 
           expect(stdout.gets(6)).to eq("custom")
-          expect(stdout.flush.read).to eq("input\n")
+          expect(stdout.flush.read).to eq("0009input")
         end
       end
 
diff --git a/spec/gitlab_shell_discover_spec.rb b/spec/gitlab_shell_discover_spec.rb
--- a/spec/gitlab_shell_discover_spec.rb
+++ b/spec/gitlab_shell_discover_spec.rb
@@ -108,7 +108,7 @@
     end
 
     it 'outputs "Only SSH allowed"' do
-      _, stderr, status = run!(["-c/usr/share/webapps/gitlab-shell/bin/gitlab-shell", "username-someuser"], env: {})
+      _, stderr, status = run!(["-c/usr/share/webapps/gitlab-shell/bin/gitlab-shell", "username-someuser"], env: {'SSH_CONNECTION' => ''})
 
       expect(stderr).to eq("Only SSH allowed\n")
       expect(status).not_to be_success
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1636757512 -3600
#      Fri Nov 12 23:51:52 2021 +0100
# Branch heptapod
# Node ID b15301c706769652286af112a4437dc8cdfac86e
# Parent  f23dd26963eecab3f2dc56638921336cc5e88db2
Heptapod CI: apply changes in pipeline as in upstream

Golang < 1.16 is not supported any more ; 1.17 appears in the build.

diff --git a/heptapod-ci.yml b/heptapod-ci.yml
--- a/heptapod-ci.yml
+++ b/heptapod-ci.yml
@@ -38,20 +38,6 @@
   script:
     - make verify test
 
-go:1.14:
-  extends: .test
-  image: golang:1.14
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
-go:1.15:
-  extends: .test
-  image: golang:1.15
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
 go:1.16:
   extends: .test
   image: golang:1.16
@@ -59,8 +45,15 @@
     - make coverage
   coverage: '/\d+.\d+%/'
 
+go:1.17:
+  extends: .test
+  image: golang:1.17
+  after_script:
+    - make coverage
+  coverage: '/\d+.\d+%/'
+
 race:
   extends: .test
-  image: golang:1.16
+  image: golang:1.17
   script:
     - make test_golang_race
# HG changeset patch
# User Georges Racinet on mutations.racinet.fr <georges@racinet.fr>
# Date 1636989848 -3600
#      Mon Nov 15 16:24:08 2021 +0100
# Branch heptapod
# Node ID 9e659daeffdf3b711ef6aaba6cf4c737bb9f632c
# Parent  b15301c706769652286af112a4437dc8cdfac86e
Added tag heptapod-13.21.0 for changeset b15301c70676

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -35,3 +35,4 @@
 aefbb5e3eac5411fc526a88d3df1762ecbdc2e76 heptapod-13.19.0
 b84ce63181d5c0d07585be867dbe957eb6d35348 heptapod-13.19.1
 f593b81abfe52ca2083440f3f470dce3d13079da heptapod-13.19.2
+b15301c706769652286af112a4437dc8cdfac86e heptapod-13.21.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1637927653 -3600
#      Fri Nov 26 12:54:13 2021 +0100
# Branch heptapod-stable
# Node ID 7a9511f85a6a3aca688269a37504d371068a2961
# Parent  29ff795406b8b544ad4be6b1dcbc3f119b955a36
# Parent  9e659daeffdf3b711ef6aaba6cf4c737bb9f632c
heptapod#582: making Heptapod 0.27 the stable series

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -52,20 +52,6 @@
   script:
     - make verify test
 
-go:1.14:
-  extends: .test
-  image: golang:1.14
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
-go:1.15:
-  extends: .test
-  image: golang:1.15
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
 go:1.16:
   extends: .test
   image: golang:1.16
@@ -73,9 +59,16 @@
     - make coverage
   coverage: '/\d+.\d+%/'
 
+go:1.17:
+  extends: .test
+  image: golang:1.17
+  after_script:
+    - make coverage
+  coverage: '/\d+.\d+%/'
+
 race:
   extends: .test
-  image: golang:1.16
+  image: golang:1.17
   script:
     - make test_golang_race
 
diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -35,3 +35,4 @@
 aefbb5e3eac5411fc526a88d3df1762ecbdc2e76 heptapod-13.19.0
 b84ce63181d5c0d07585be867dbe957eb6d35348 heptapod-13.19.1
 f593b81abfe52ca2083440f3f470dce3d13079da heptapod-13.19.2
+b15301c706769652286af112a4437dc8cdfac86e heptapod-13.21.0
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,25 @@
+v13.21.1
+
+- Only validate SSL cert file exists if a value is supplied !527
+
+v13.21.0
+
+- Change default logging format to JSON !476
+- Shutdown sshd gracefully !484
+- Provide liveness and readiness probes !494
+- Add tracing instrumentation to http client !495
+- Log same correlation_id on auth keys check of ssh connections !501
+- fix: validate client cert paths exist on disk before proceeding !508
+- Modify regex to prevent partial matches
+
+v13.20.0
+
+- Remove bin/authorized_keys !491
+- Add a make install command !490
+- Create PROCESS.md page with Security release process !488
+- Fix the Geo SSH push proxy hanging !487
+- Standardize logging timestamp format !485
+
 v13.19.1
 
 - Modify regex to prevent partial matches
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -9,6 +9,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 13.21.0
+
+- Jump to upstream GitLab Shell v13.21.1 (GitLab 14.4 series)
+
 ## 13.19.2
 
 - Added support for the new `NO_GIT` flag, telling Mercurial not to
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-13.19.2
+13.21.0
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: validate verify verify_ruby verify_golang test test_ruby test_golang coverage coverage_golang setup _install build compile check clean
+.PHONY: validate verify verify_ruby verify_golang test test_ruby test_golang coverage coverage_golang setup _script_install build compile check clean install
 
 GO_SOURCES := $(shell find . -name '*.go')
 VERSION_STRING := $(shell ./compute_version || echo unknown)
@@ -6,12 +6,17 @@
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)"
 
+PREFIX ?= /usr/local
+
 validate: verify test
 
 verify: verify_golang
 
 verify_golang:
-	gofmt -s -l $(GO_SOURCES)
+	gofmt -s -l $(GO_SOURCES) | awk '{ print } END { if (NR > 0) { print "Please run make fmt"; exit 1 } }'
+
+fmt:
+	gofmt -w -s $(GO_SOURCES)
 
 test: test_ruby test_golang
 
@@ -30,9 +35,9 @@
 coverage_golang:
 	[ -f cover.out ] && go tool cover -func cover.out
 
-setup: _install bin/gitlab-shell
+setup: _script_install bin/gitlab-shell
 
-_install:
+_script_install:
 	bin/install
 
 build: bin/gitlab-shell
@@ -45,3 +50,12 @@
 
 clean:
 	rm -f bin/check bin/gitlab-shell bin/gitlab-shell-authorized-keys-check bin/gitlab-shell-authorized-principals-check bin/gitlab-sshd
+
+install: compile
+	mkdir -p $(DESTDIR)$(PREFIX)/bin/
+	install -m755 bin/check $(DESTDIR)$(PREFIX)/bin/check
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-keys-check
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-principals-check
+	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-sshd
+
diff --git a/PROCESS.md b/PROCESS.md
new file mode 100644
--- /dev/null
+++ b/PROCESS.md
@@ -0,0 +1,44 @@
+## Releasing a new version
+
+GitLab Shell is versioned by git tags, and the version used by the Rails
+application is stored in
+[`GITLAB_SHELL_VERSION`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_SHELL_VERSION).
+
+For each version, there is a raw version and a tag version:
+
+- The **raw version** is the version number. For instance, `15.2.8`.
+- The **tag version** is the raw version prefixed with `v`. For instance, `v15.2.8`.
+
+To release a new version of GitLab Shell and have that version available to the
+Rails application:
+
+1. Create a merge request to update the [`CHANGELOG`](CHANGELOG) with the
+   **tag version** and the [`VERSION`](VERSION) file with the **raw version**.
+2. Ask a maintainer to review and merge the merge request. If you're already a
+   maintainer, second maintainer review is not required.
+3. Add a new git tag with the **tag version**.
+4. Update `GITLAB_SHELL_VERSION` in the Rails application to the **raw
+   version**. (Note: this can be done as a separate MR to that, or in and MR
+   that will make use of the latest GitLab Shell changes.)
+
+## Security releases
+
+GitLab Shell is included in the packages we create for GitLab, and each version of GitLab specifies the version of GitLab Shell it uses in the `GITLAB_SHELL_VERSION` file, so security fixes in GitLab Shell are tightly coupled to the [GitLab security release](https://about.gitlab.com/handbook/engineering/workflow/#security-issues) workflow.
+
+For a security fix in GitLab Shell, two sets of merge requests are required:
+
+* The fix itself, in the `gitlab-org/security/gitlab-shell` repository and its backports to the previous versions of GitLab Shell
+* Merge requests to change the versions of GitLab Shell included in the GitLab security release, in the `gitlab-org/security/gitlab` repository
+
+The first step could be to create a merge request with a fix targeting `main` in `gitlab-org/security/gitlab-shell`. When the merge request is approved by maintainers, backports targeting previous 3 versions of GitLab Shell must be created. The stable branches for those versions may not exist, feel free to ask a maintainer to create ones. The stable branches must be created out of the GitLab Shell tags/version used by the 3 previous GitLab releases. In order to find out the GitLab Shell version that is used on a particular GitLab stable release, the following steps may be helpful:
+
+```shell
+git fetch security 13-9-stable-ee
+git show refs/remotes/security/13-9-stable-ee:GITLAB_SHELL_VERSION
+```
+
+These steps display the version that is used by `13.9` version of GitLab.
+
+Close to the GitLab security release, a maintainer should merge the fix and backports and cut all the necessary GitLab Shell versions. This allows bumping the `GITLAB_SHELL_VERSION` for `gitlab-org/security/gitlab`. The GitLab merge request will be handled by the general GitLab security release process.
+
+Once the security release is done, a GitLab Shell maintainer is responsible for syncing tags and `main` to the `gitlab-org/gitlab-shell` repository.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
 
 When you access the GitLab server over SSH then GitLab Shell will:
 
-1. Limits you to predefined git commands (git push, git pull).
+1. Limit you to predefined git commands (git push, git pull).
 1. Call the GitLab Rails API to check if you are authorized, and what Gitaly server your repository is on
 1. Copy data back and forth between the SSH client and the Gitaly server
 
@@ -38,16 +38,36 @@
 We follow the [Golang Release Policy](https://golang.org/doc/devel/release.html#policy)
 of supporting the current stable version and the previous two major versions.
 
-## Setup
-
-    make setup
-
 ## Check
 
 Checks if GitLab API access and redis via internal API can be reached:
 
     make check
 
+## Compile
+
+Builds the `gitlab-shell` binaries, placing them into `bin/`.
+
+    make compile
+
+## Install
+
+Builds the `gitlab-shell` binaries and installs them onto the filesystem. The
+default location is `/usr/local`, but can be controlled by use of the `PREFIX`
+and `DESTDIR` environment variables.
+
+    make install
+
+## Setup
+
+This command is intended for use when installing GitLab from source on a single
+machine. In addition to compiling the gitlab-shell binaries, it ensures that
+various paths on the filesystem exist with the correct permissions. Do not run
+it unless instructed to by your installation method documentation.
+
+    make setup
+
+
 ## Testing
 
 Run tests:
@@ -82,28 +102,9 @@
 
 Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
 
-## Releasing a new version
-
-GitLab Shell is versioned by git tags, and the version used by the Rails
-application is stored in
-[`GITLAB_SHELL_VERSION`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_SHELL_VERSION).
-
-For each version, there is a raw version and a tag version:
-
-- The **raw version** is the version number. For instance, `15.2.8`.
-- The **tag version** is the raw version prefixed with `v`. For instance, `v15.2.8`.
+## Releasing
 
-To release a new version of GitLab Shell and have that version available to the
-Rails application:
-
-1. Create a merge request to update the [`CHANGELOG`](CHANGELOG) with the
-   **tag version** and the [`VERSION`](VERSION) file with the **raw version**.
-2. Ask a maintainer to review and merge the merge request. If you're already a
-   maintainer, second maintainer review is not required.
-3. Add a new git tag with the **tag version**.
-4. Update `GITLAB_SHELL_VERSION` in the Rails application to the **raw
-   version**. (Note: this can be done as a separate MR to that, or in and MR
-   that will make use of the latest GitLab Shell changes.)
+See [PROCESS.md](./PROCESS.md)
 
 ## Contributing
 
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.19.1
+13.21.1
diff --git a/bin/authorized_keys b/bin/authorized_keys
deleted file mode 100755
--- a/bin/authorized_keys
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/sh
-
-# Legacy script used for AuthorizedKeysCommand when configured without username.
-# Executes gitlab-shell-authorized-keys-check with "git" as expected and actual
-# username and with the passed key.
-#
-# TODO: Remove this in https://gitlab.com/gitlab-org/gitlab-shell/issues/209.
-
-$(dirname $0)/gitlab-shell-authorized-keys-check git git $1
diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -11,16 +11,14 @@
 	"strings"
 	"testing"
 
-	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
+
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
 
 func TestClients(t *testing.T) {
-	testDirCleanup, err := testhelper.PrepareTestRootDir()
-	require.NoError(t, err)
-	defer testDirCleanup()
+	testhelper.PrepareTestRootDir(t)
 
 	testCases := []struct {
 		desc            string
@@ -61,7 +59,8 @@
 
 			secret := "sssh, it's a secret"
 
-			httpClient := NewHTTPClient(url, tc.relativeURLRoot, tc.caFile, "", false, 1)
+			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", false, 1, nil)
+			require.NoError(t, err)
 
 			client, err := NewGitlabNetClient("", "", secret, httpClient)
 			require.NoError(t, err)
@@ -78,7 +77,6 @@
 
 func testSuccessfulGet(t *testing.T, client *GitlabNetClient) {
 	t.Run("Successful get", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		response, err := client.Get(context.Background(), "/hello")
 		require.NoError(t, err)
 		require.NotNil(t, response)
@@ -88,22 +86,11 @@
 		responseBody, err := ioutil.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, string(responseBody), "Hello")
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=GET")
-		require.Contains(t, entries[0].Message, "status=200")
-		require.Contains(t, entries[0].Message, "content_length_bytes=")
-		require.Contains(t, entries[0].Message, "Finished HTTP request")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
 func testSuccessfulPost(t *testing.T, client *GitlabNetClient) {
 	t.Run("Successful Post", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		data := map[string]string{"key": "value"}
 
 		response, err := client.Post(context.Background(), "/post_endpoint", data)
@@ -115,50 +102,20 @@
 		responseBody, err := ioutil.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, "Echo: {\"key\":\"value\"}", string(responseBody))
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=POST")
-		require.Contains(t, entries[0].Message, "status=200")
-		require.Contains(t, entries[0].Message, "content_length_bytes=")
-		require.Contains(t, entries[0].Message, "Finished HTTP request")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
 func testMissing(t *testing.T, client *GitlabNetClient) {
 	t.Run("Missing error for GET", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		response, err := client.Get(context.Background(), "/missing")
 		require.EqualError(t, err, "Internal API error (404)")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=GET")
-		require.Contains(t, entries[0].Message, "status=404")
-		require.Contains(t, entries[0].Message, "Internal API error")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 
 	t.Run("Missing error for POST", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
 		response, err := client.Post(context.Background(), "/missing", map[string]string{})
 		require.EqualError(t, err, "Internal API error (404)")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=POST")
-		require.Contains(t, entries[0].Message, "status=404")
-		require.Contains(t, entries[0].Message, "Internal API error")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
@@ -178,37 +135,15 @@
 
 func testBrokenRequest(t *testing.T, client *GitlabNetClient) {
 	t.Run("Broken request for GET", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
-
 		response, err := client.Get(context.Background(), "/broken")
 		require.EqualError(t, err, "Internal API unreachable")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=GET")
-		require.NotContains(t, entries[0].Message, "status=")
-		require.Contains(t, entries[0].Message, "Internal API unreachable")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 
 	t.Run("Broken request for POST", func(t *testing.T) {
-		hook := testhelper.SetupLogger()
-
 		response, err := client.Post(context.Background(), "/broken", map[string]string{})
 		require.EqualError(t, err, "Internal API unreachable")
 		require.Nil(t, response)
-
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 1, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[0].Level)
-		require.Contains(t, entries[0].Message, "method=POST")
-		require.NotContains(t, entries[0].Message, "status=")
-		require.Contains(t, entries[0].Message, "Internal API unreachable")
-		require.Contains(t, entries[0].Message, "correlation_id=")
 	})
 }
 
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -11,9 +11,7 @@
 	"strings"
 	"time"
 
-	"gitlab.com/gitlab-org/labkit/correlation"
-
-	log "github.com/sirupsen/logrus"
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
@@ -71,12 +69,12 @@
 	return path
 }
 
-func newRequest(ctx context.Context, method, host, path string, data interface{}) (*http.Request, string, error) {
+func newRequest(ctx context.Context, method, host, path string, data interface{}) (*http.Request, error) {
 	var jsonReader io.Reader
 	if data != nil {
 		jsonData, err := json.Marshal(data)
 		if err != nil {
-			return nil, "", err
+			return nil, err
 		}
 
 		jsonReader = bytes.NewReader(jsonData)
@@ -84,12 +82,10 @@
 
 	request, err := http.NewRequestWithContext(ctx, method, host+path, jsonReader)
 	if err != nil {
-		return nil, "", err
+		return nil, err
 	}
 
-	correlationID := correlation.ExtractFromContext(ctx)
-
-	return request, correlationID, nil
+	return request, nil
 }
 
 func parseError(resp *http.Response) error {
@@ -116,7 +112,7 @@
 }
 
 func (c *GitlabNetClient) DoRequest(ctx context.Context, method, path string, data interface{}) (*http.Response, error) {
-	request, correlationID, err := newRequest(ctx, method, c.httpClient.Host, path, data)
+	request, err := newRequest(ctx, method, c.httpClient.Host, path, data)
 	if err != nil {
 		return nil, err
 	}
@@ -136,12 +132,11 @@
 	start := time.Now()
 	response, err := c.httpClient.Do(request)
 	fields := log.Fields{
-		"correlation_id": correlationID,
-		"method":         method,
-		"url":            request.URL.String(),
-		"duration_ms":    time.Since(start) / time.Millisecond,
+		"method":      method,
+		"url":         request.URL.String(),
+		"duration_ms": time.Since(start) / time.Millisecond,
 	}
-	logger := log.WithFields(fields)
+	logger := log.WithContextFields(ctx, fields)
 
 	if err != nil {
 		logger.WithError(err).Error("Internal API unreachable")
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -5,15 +5,18 @@
 	"crypto/tls"
 	"crypto/x509"
 	"errors"
+	"fmt"
 	"io/ioutil"
 	"net"
 	"net/http"
+	"os"
 	"path/filepath"
 	"strings"
 	"time"
 
-	log "github.com/sirupsen/logrus"
 	"gitlab.com/gitlab-org/labkit/correlation"
+	"gitlab.com/gitlab-org/labkit/log"
+	"gitlab.com/gitlab-org/labkit/tracing"
 )
 
 const (
@@ -24,6 +27,10 @@
 	defaultReadTimeoutSeconds = 300
 )
 
+var (
+	ErrCafileNotFound = errors.New("cafile not found")
+)
+
 type HttpClient struct {
 	*http.Client
 	Host string
@@ -48,6 +55,22 @@
 	}
 }
 
+func validateCaFile(filename string) error {
+	if filename == "" {
+		return nil
+	}
+
+	if _, err := os.Stat(filename); err != nil {
+		if os.IsNotExist(err) {
+			return fmt.Errorf("cannot find cafile '%s': %w", filename, ErrCafileNotFound)
+		}
+
+		return err
+	}
+
+	return nil
+}
+
 // Deprecated: use NewHTTPClientWithOpts - https://gitlab.com/gitlab-org/gitlab-shell/-/issues/484
 func NewHTTPClient(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64) *HttpClient {
 	c, err := NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath, selfSignedCert, readTimeoutSeconds, nil)
@@ -59,15 +82,6 @@
 
 // NewHTTPClientWithOpts builds an HTTP client using the provided options
 func NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64, opts []HTTPClientOpt) (*HttpClient, error) {
-	hcc := &httpClientCfg{
-		caFile: caFile,
-		caPath: caPath,
-	}
-
-	for _, opt := range opts {
-		opt(hcc)
-	}
-
 	var transport *http.Transport
 	var host string
 	var err error
@@ -76,6 +90,20 @@
 	} else if strings.HasPrefix(gitlabURL, httpProtocol) {
 		transport, host = buildHttpTransport(gitlabURL)
 	} else if strings.HasPrefix(gitlabURL, httpsProtocol) {
+		err = validateCaFile(caFile)
+		if err != nil {
+			return nil, err
+		}
+
+		hcc := &httpClientCfg{
+			caFile: caFile,
+			caPath: caPath,
+		}
+
+		for _, opt := range opts {
+			opt(hcc)
+		}
+
 		transport, host, err = buildHttpsTransport(*hcc, selfSignedCert, gitlabURL)
 		if err != nil {
 			return nil, err
@@ -85,7 +113,7 @@
 	}
 
 	c := &http.Client{
-		Transport: correlation.NewInstrumentedRoundTripper(transport),
+		Transport: correlation.NewInstrumentedRoundTripper(tracing.NewRoundTripper(transport)),
 		Timeout:   readTimeout(readTimeoutSeconds),
 	}
 
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -17,7 +17,8 @@
 func TestReadTimeout(t *testing.T) {
 	expectedSeconds := uint64(300)
 
-	client := NewHTTPClient("http://localhost:3000", "", "", "", false, expectedSeconds)
+	client, err := NewHTTPClientWithOpts("http://localhost:3000", "", "", "", false, expectedSeconds, nil)
+	require.NoError(t, err)
 
 	require.NotNil(t, client)
 	require.Equal(t, time.Duration(expectedSeconds)*time.Second, client.Client.Timeout)
@@ -122,7 +123,8 @@
 func setup(t *testing.T, username, password string, requests []testserver.TestRequestHandler) *GitlabNetClient {
 	url := testserver.StartHttpServer(t, requests)
 
-	httpClient := NewHTTPClient(url, "", "", "", false, 1)
+	httpClient, err := NewHTTPClientWithOpts(url, "", "", "", false, 1, nil)
+	require.NoError(t, err)
 
 	client, err := NewGitlabNetClient(username, password, "", httpClient)
 	require.NoError(t, err)
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -28,10 +28,7 @@
 		{
 			desc:   "Valid CaPath",
 			caPath: path.Join(testhelper.TestRoot, "certs/valid"),
-		},
-		{
-			desc:       "Self signed cert option enabled",
-			selfSigned: true,
+			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
 		},
 		{
 			desc:       "Invalid cert with self signed cert option enabled",
@@ -51,7 +48,8 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath, tc.selfSigned)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath, tc.selfSigned)
+			require.NoError(t, err)
 
 			response, err := client.Get(context.Background(), "/hello")
 			require.NoError(t, err)
@@ -68,39 +66,51 @@
 
 func TestFailedRequests(t *testing.T) {
 	testCases := []struct {
-		desc   string
-		caFile string
-		caPath string
+		desc                   string
+		caFile                 string
+		caPath                 string
+		expectedCaFileNotFound bool
+		expectedError          string
 	}{
 		{
-			desc:   "Invalid CaFile",
-			caFile: path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+			desc:          "Invalid CaFile",
+			caFile:        path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+			expectedError: "Internal API unreachable",
 		},
 		{
-			desc:   "Invalid CaPath",
-			caPath: path.Join(testhelper.TestRoot, "certs/invalid"),
+			desc:                   "Missing CaFile",
+			caFile:                 path.Join(testhelper.TestRoot, "certs/invalid/missing.crt"),
+			expectedCaFileNotFound: true,
 		},
 		{
-			desc: "Empty config",
+			desc:          "Invalid CaPath",
+			caPath:        path.Join(testhelper.TestRoot, "certs/invalid"),
+			expectedError: "Internal API unreachable",
+		},
+		{
+			desc:          "Empty config",
+			expectedError: "Internal API unreachable",
 		},
 	}
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "", false)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "", false)
+			if tc.expectedCaFileNotFound {
+				require.Error(t, err)
+				require.ErrorIs(t, err, ErrCafileNotFound)
+			} else {
+				_, err = client.Get(context.Background(), "/hello")
+				require.Error(t, err)
 
-			_, err := client.Get(context.Background(), "/hello")
-			require.Error(t, err)
-
-			require.Equal(t, err.Error(), "Internal API unreachable")
+				require.Equal(t, err.Error(), tc.expectedError)
+			}
 		})
 	}
 }
 
-func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string, selfSigned bool) *GitlabNetClient {
-	testDirCleanup, err := testhelper.PrepareTestRootDir()
-	require.NoError(t, err)
-	defer testDirCleanup()
+func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string, selfSigned bool) (*GitlabNetClient, error) {
+	testhelper.PrepareTestRootDir(t)
 
 	requests := []testserver.TestRequestHandler{
 		{
@@ -121,10 +131,11 @@
 	}
 
 	httpClient, err := NewHTTPClientWithOpts(url, "", caFile, caPath, selfSigned, 1, opts)
-	require.NoError(t, err)
+	if err != nil {
+		return nil, err
+	}
 
 	client, err := NewGitlabNetClient("", "", "", httpClient)
-	require.NoError(t, err)
 
-	return client
+	return client, err
 }
diff --git a/cmd/check/main.go b/cmd/check/main.go
--- a/cmd/check/main.go
+++ b/cmd/check/main.go
@@ -31,7 +31,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
 	if err != nil {
diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go
--- a/cmd/gitlab-shell-authorized-keys-check/main.go
+++ b/cmd/gitlab-shell-authorized-keys-check/main.go
@@ -32,7 +32,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
 	if err != nil {
diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go
--- a/cmd/gitlab-shell-authorized-principals-check/main.go
+++ b/cmd/gitlab-shell-authorized-principals-check/main.go
@@ -32,7 +32,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
 	if err != nil {
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -46,7 +46,8 @@
 		os.Exit(1)
 	}
 
-	logger.Configure(config)
+	logCloser := logger.Configure(config)
+	defer logCloser.Close()
 
 	env := sshenv.NewFromEnv()
 	cmd, err := command.New(executable, os.Args[1:], env, config, readWriter)
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -95,9 +95,7 @@
 	t.Helper()
 
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		testDirCleanup, err := testhelper.PrepareTestRootDir()
-		require.NoError(t, err)
-		defer testDirCleanup()
+		testhelper.PrepareTestRootDir(t)
 
 		t.Logf("gitlab-api-mock: received request: %s %s", r.Method, r.RequestURI)
 		w.Header().Set("Content-Type", "application/json")
@@ -231,7 +229,7 @@
 	t.Cleanup(func() { pw.Close() })
 
 	scanner := bufio.NewScanner(pr)
-	extractor := regexp.MustCompile(`msg="Listening on ([0-9a-f\[\]\.:]+)"`)
+	extractor := regexp.MustCompile(`"tcp_address":"([0-9a-f\[\]\.:]+)"`)
 
 	ctx, cancel := context.WithCancel(context.Background())
 	cmd := exec.CommandContext(ctx, sshdPath, "-config-dir", dir)
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -1,15 +1,19 @@
 package main
 
 import (
+	"context"
 	"flag"
 	"os"
-
-	log "github.com/sirupsen/logrus"
+	"os/signal"
+	"syscall"
+	"time"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshd"
+
+	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/monitoring"
 )
 
@@ -45,37 +49,65 @@
 		var err error
 		cfg, err = config.NewFromDir(*configDir)
 		if err != nil {
-			log.Fatalf("failed to load configuration from specified directory: %v", err)
+			log.WithError(err).Fatal("failed to load configuration from specified directory")
 		}
 	}
 	overrideConfigFromEnvironment(cfg)
 	if err := cfg.IsSane(); err != nil {
 		if *configDir == "" {
-			log.Warn("note: no config-dir provided, using only environment variables")
+			log.WithError(err).Fatal("no config-dir provided, using only environment variables")
+		} else {
+			log.WithError(err).Fatal("configuration error")
 		}
-		log.Fatalf("configuration error: %v", err)
 	}
 
 	cfg.ApplyGlobalState()
 
-	logger.ConfigureStandalone(cfg)
+	logCloser := logger.ConfigureStandalone(cfg)
+	defer logCloser.Close()
 
 	ctx, finished := command.Setup("gitlab-sshd", cfg)
 	defer finished()
 
+	server, err := sshd.NewServer(cfg)
+	if err != nil {
+		log.WithError(err).Fatal("Failed to start GitLab built-in sshd")
+	}
+
 	// Startup monitoring endpoint.
 	if cfg.Server.WebListen != "" {
 		go func() {
-			log.Fatal(
-				monitoring.Start(
-					monitoring.WithListenerAddress(cfg.Server.WebListen),
-					monitoring.WithBuildInformation(Version, BuildTime),
-				),
+			err := monitoring.Start(
+				monitoring.WithListenerAddress(cfg.Server.WebListen),
+				monitoring.WithBuildInformation(Version, BuildTime),
+				monitoring.WithServeMux(server.MonitoringServeMux()),
 			)
+
+			log.WithError(err).Fatal("monitoring service raised an error")
 		}()
 	}
 
-	if err := sshd.Run(ctx, cfg); err != nil {
-		log.Fatalf("Failed to start GitLab built-in sshd: %v", err)
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	done := make(chan os.Signal, 1)
+	signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
+
+	go func() {
+		sig := <-done
+		signal.Reset(syscall.SIGINT, syscall.SIGTERM)
+
+		log.WithFields(log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
+
+		server.Shutdown()
+
+		<-time.After(cfg.Server.GracePeriod())
+
+		cancel()
+
+	}()
+
+	if err := server.ListenAndServe(ctx); err != nil {
+		log.WithError(err).Fatal("GitLab built-in sshd failed to listen for new connections")
 	}
 }
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -66,7 +66,7 @@
 # Log level. INFO by default
 log_level: INFO
 
-# Log format. 'text' by default
+# Log format. 'json' by default, can be changed to 'text' if needed
 # log_format: json
 
 # Audit usernames.
@@ -89,7 +89,7 @@
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
-  # SSH host key files. 
+  # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
     - /run/secrets/ssh-hostkeys/ssh_host_ecdsa_key
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module gitlab.com/gitlab-org/gitlab-shell
 
-go 1.13
+go 1.16
 
 require (
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
@@ -8,12 +8,11 @@
 	github.com/mattn/go-shellwords v1.0.11
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
-	github.com/pires/go-proxyproto v0.5.0
+	github.com/pires/go-proxyproto v0.6.0
 	github.com/prometheus/client_golang v1.10.0
-	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1
-	gitlab.com/gitlab-org/labkit v1.4.1
+	gitlab.com/gitlab-org/labkit v1.7.0
 	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/grpc v1.37.0
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -16,6 +16,7 @@
 cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
 cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
 cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
 cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
@@ -38,6 +39,8 @@
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -81,6 +84,8 @@
 github.com/avast/retry-go v2.4.2+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.37.0 h1:GzFnhOIsrGyQ69s7VgqtrG2BG8v7X7vwB3Xpbd/DBBk=
+github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -91,6 +96,8 @@
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 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=
 github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -155,7 +162,6 @@
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
 github.com/getsentry/raven-go v0.1.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
@@ -173,7 +179,6 @@
 github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
 github.com/git-lfs/wildmatch v1.0.4/go.mod h1:SdHAGnApDpnFYQ0vAxbniWR0sn7yLJ3QXo9RRfhn2ew=
 github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
-github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -212,8 +217,9 @@
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -260,10 +266,8 @@
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -274,6 +278,7 @@
 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210125172800-10e9aeb4a998/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo=
@@ -343,6 +348,10 @@
 github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
 github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@@ -485,7 +494,6 @@
 github.com/otiai10/copy v1.4.2 h1:RTiz2sol3eoXPLF4o+YWqEybwfUa/Q2Nkc4ZIUs3fwI=
 github.com/otiai10/copy v1.4.2/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E=
 github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
-github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI=
 github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
 github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw=
 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
@@ -503,10 +511,9 @@
 github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
-github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
-github.com/pires/go-proxyproto v0.5.0 h1:A4Jv4ZCaV3AFJeGh5mGwkz4iuWUYMlQ7IoO/GTuSuLo=
-github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pires/go-proxyproto v0.6.0 h1:cLJUPnuQdiNf7P/wbeOKmM1khVdaMgTFDLj8h9ZrVYk=
+github.com/pires/go-proxyproto v0.6.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -645,8 +652,8 @@
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
-gitlab.com/gitlab-org/labkit v1.4.1 h1:mh4g+c/esQSWCVQJ197aftrIbXlhXCtiLC8RgSvyCx0=
-gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
+gitlab.com/gitlab-org/labkit v1.7.0 h1:ufG42cvJ+VkqaUWVEoat/h9PGcW9FFSUPQFz7uwL5wc=
+gitlab.com/gitlab-org/labkit v1.7.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
@@ -657,6 +664,7 @@
 go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -699,7 +707,6 @@
 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
@@ -771,6 +778,7 @@
 golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
@@ -783,6 +791,7 @@
 golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
@@ -937,6 +946,7 @@
 golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -945,9 +955,7 @@
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
-gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
-gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
@@ -969,6 +977,7 @@
 google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
 google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.37.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
@@ -1020,6 +1029,8 @@
 google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
diff --git a/heptapod-ci.yml b/heptapod-ci.yml
--- a/heptapod-ci.yml
+++ b/heptapod-ci.yml
@@ -38,20 +38,6 @@
   script:
     - make verify test
 
-go:1.14:
-  extends: .test
-  image: golang:1.14
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
-go:1.15:
-  extends: .test
-  image: golang:1.15
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
 go:1.16:
   extends: .test
   image: golang:1.16
@@ -59,8 +45,15 @@
     - make coverage
   coverage: '/\d+.\d+%/'
 
+go:1.17:
+  extends: .test
+  image: golang:1.17
+  after_script:
+    - make coverage
+  coverage: '/\d+.\d+%/'
+
 race:
   extends: .test
-  image: golang:1.16
+  image: golang:1.17
   script:
     - make test_golang_race
diff --git a/internal/command/hg/hg.go b/internal/command/hg/hg.go
--- a/internal/command/hg/hg.go
+++ b/internal/command/hg/hg.go
@@ -83,7 +83,7 @@
 		"HEPTAPOD_NO_GIT=" + strconv.FormatBool(response.NoGit),
 	}...)
 	if c.Config.Mercurial.ModulePolicy != "" {
-		env = append(env, "HGMODULEPOLICY=" + c.Config.Mercurial.ModulePolicy)
+		env = append(env, "HGMODULEPOLICY="+c.Config.Mercurial.ModulePolicy)
 	}
 
 	hg_exe := c.Config.Mercurial.BinPath()
diff --git a/internal/command/hg/hg_test.go b/internal/command/hg/hg_test.go
--- a/internal/command/hg/hg_test.go
+++ b/internal/command/hg/hg_test.go
@@ -79,8 +79,8 @@
 	}
 
 	// cannot directly take a reference to false and true
-	no := false;
-	yes := true;
+	no := false
+	yes := true
 
 	testcases := []testcase{
 		{native_input: nil, native_expected: "false", no_git_expected: "false", module_policy: "c"},
@@ -103,7 +103,7 @@
 				GitlabUrl: url,
 				Mercurial: config.MercurialConfig{
 					OptionalBinPath: "hg5.4-py3",
-					ModulePolicy: tc.module_policy,
+					ModulePolicy:    tc.module_policy,
 					Hgrc:            []string{"/usr/share/shipped.hgrc", "/etc/main.hgrc"}},
 			},
 			Args:       &commandargs.Shell{GitlabKeyId: "key-12", SshArgs: []string{"hg", "-R", "my/group/awesome-proj", "serve", "--stdio"}},
diff --git a/internal/command/receivepack/gitalycall_test.go b/internal/command/receivepack/gitalycall_test.go
--- a/internal/command/receivepack/gitalycall_test.go
+++ b/internal/command/receivepack/gitalycall_test.go
@@ -5,7 +5,6 @@
 	"context"
 	"testing"
 
-	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
@@ -14,12 +13,11 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
 )
 
 func TestReceivePack(t *testing.T) {
-	gitalyAddress, _ := testserver.StartGitalyServer(t)
+	gitalyAddress, testServer := testserver.StartGitalyServer(t)
 
 	requests := requesthandlers.BuildAllowedWithGitalyHandlers(t, gitalyAddress)
 	url := testserver.StartHttpServer(t, requests)
@@ -43,10 +41,15 @@
 
 		env := sshenv.Env{
 			IsSSHConnection: true,
-			OriginalCommand: "git-receive-pack group/repo",
+			OriginalCommand: "git-receive-pack " + repo,
 			RemoteAddr:      "127.0.0.1",
 		}
-		args := &commandargs.Shell{CommandType: commandargs.ReceivePack, SshArgs: []string{"git-receive-pack", repo}, Env: env}
+
+		args := &commandargs.Shell{
+			CommandType: commandargs.ReceivePack,
+			SshArgs:     []string{"git-receive-pack", repo},
+			Env:         env,
+		}
 
 		if tc.username != "" {
 			args.GitlabUsername = tc.username
@@ -60,7 +63,6 @@
 			ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 		}
 
-		hook := testhelper.SetupLogger()
 		ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 		ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
@@ -73,15 +75,20 @@
 			require.Equal(t, "ReceivePack: key-123 "+repo, output.String())
 		}
 
-		require.True(t, testhelper.WaitForLogEvent(hook))
-		entries := hook.AllEntries()
-		require.Equal(t, 2, len(entries))
-		require.Equal(t, logrus.InfoLevel, entries[1].Level)
-		require.Contains(t, entries[1].Message, "executing git command")
-		require.Contains(t, entries[1].Message, "command=git-receive-pack")
-		require.Contains(t, entries[1].Message, "remote_ip=127.0.0.1")
-		require.Contains(t, entries[1].Message, "gl_key_type=key")
-		require.Contains(t, entries[1].Message, "gl_key_id=123")
-		require.Contains(t, entries[1].Message, "correlation_id=a-correlation-id")
+		for k, v := range map[string]string{
+			"gitaly-feature-cache_invalidator":        "true",
+			"gitaly-feature-inforef_uploadpack_cache": "false",
+			"x-gitlab-client-name":                    "gitlab-shell-tests-git-receive-pack",
+			"key_id":                                  "123",
+			"user_id":                                 "1",
+			"remote_ip":                               "127.0.0.1",
+			"key_type":                                "key",
+		} {
+			actual := testServer.ReceivedMD[k]
+			require.Len(t, actual, 1)
+			require.Equal(t, v, actual[0])
+		}
+		require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+		require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 	}
 }
diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -10,12 +10,13 @@
 	"io"
 	"net/http"
 
-	log "github.com/sirupsen/logrus"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/pktline"
+
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 type Request struct {
@@ -112,10 +113,32 @@
 }
 
 func (c *Command) readFromStdin() ([]byte, error) {
-	output := new(bytes.Buffer)
-	_, err := io.Copy(output, c.ReadWriter.In)
+	var output []byte
+	var needsPackData bool
+
+	scanner := pktline.NewScanner(c.ReadWriter.In)
+	for scanner.Scan() {
+		line := scanner.Bytes()
+		output = append(output, line...)
+
+		if pktline.IsFlush(line) {
+			break
+		}
 
-	return output.Bytes(), err
+		if !needsPackData && !pktline.IsRefRemoval(line) {
+			needsPackData = true
+		}
+	}
+
+	if needsPackData {
+		packData := new(bytes.Buffer)
+		_, err := io.Copy(packData, c.ReadWriter.In)
+
+		output = append(output, packData.Bytes()...)
+		return output, err
+	} else {
+		return output, nil
+	}
 }
 
 func (c *Command) readFromStdinNoEOF() []byte {
diff --git a/internal/command/shared/customaction/customaction_test.go b/internal/command/shared/customaction/customaction_test.go
--- a/internal/command/shared/customaction/customaction_test.go
+++ b/internal/command/shared/customaction/customaction_test.go
@@ -46,7 +46,7 @@
 				require.NoError(t, json.Unmarshal(b, &request))
 
 				require.Equal(t, request.Data.UserId, who)
-				require.Equal(t, "input", string(request.Output))
+				require.Equal(t, "0009input", string(request.Output))
 
 				err = json.NewEncoder(w).Encode(Response{Result: []byte("output")})
 				require.NoError(t, err)
@@ -58,7 +58,7 @@
 
 	outBuf := &bytes.Buffer{}
 	errBuf := &bytes.Buffer{}
-	input := bytes.NewBufferString("input")
+	input := bytes.NewBufferString("0009input")
 
 	response := &accessverifier.Response{
 		Who: who,
diff --git a/internal/command/uploadarchive/gitalycall_test.go b/internal/command/uploadarchive/gitalycall_test.go
--- a/internal/command/uploadarchive/gitalycall_test.go
+++ b/internal/command/uploadarchive/gitalycall_test.go
@@ -5,7 +5,6 @@
 	"context"
 	"testing"
 
-	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
@@ -13,12 +12,12 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
 )
 
-func TestUploadPack(t *testing.T) {
-	gitalyAddress, _ := testserver.StartGitalyServer(t)
+func TestUploadArchive(t *testing.T) {
+	gitalyAddress, testServer := testserver.StartGitalyServer(t)
 
 	requests := requesthandlers.BuildAllowedWithGitalyHandlers(t, gitalyAddress)
 	url := testserver.StartHttpServer(t, requests)
@@ -29,13 +28,25 @@
 	userId := "1"
 	repo := "group/repo"
 
+	env := sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: "git-upload-archive " + repo,
+		RemoteAddr:      "127.0.0.1",
+	}
+
+	args := &commandargs.Shell{
+		GitlabKeyId: userId,
+		CommandType: commandargs.UploadArchive,
+		SshArgs:     []string{"git-upload-archive", repo},
+		Env:         env,
+	}
+
 	cmd := &Command{
 		Config:     &config.Config{GitlabUrl: url},
-		Args:       &commandargs.Shell{GitlabKeyId: userId, CommandType: commandargs.UploadArchive, SshArgs: []string{"git-upload-archive", repo}},
+		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
 
-	hook := testhelper.SetupLogger()
 	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
@@ -44,13 +55,19 @@
 
 	require.Equal(t, "UploadArchive: "+repo, output.String())
 
-	require.True(t, testhelper.WaitForLogEvent(hook))
-	entries := hook.AllEntries()
-	require.Equal(t, 2, len(entries))
-	require.Equal(t, logrus.InfoLevel, entries[1].Level)
-	require.Contains(t, entries[1].Message, "executing git command")
-	require.Contains(t, entries[1].Message, "command=git-upload-archive")
-	require.Contains(t, entries[1].Message, "gl_key_type=key")
-	require.Contains(t, entries[1].Message, "gl_key_id=123")
-	require.Contains(t, entries[1].Message, "correlation_id=a-correlation-id")
+	for k, v := range map[string]string{
+		"gitaly-feature-cache_invalidator":        "true",
+		"gitaly-feature-inforef_uploadpack_cache": "false",
+		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-archive",
+		"key_id":                                  "123",
+		"user_id":                                 "1",
+		"remote_ip":                               "127.0.0.1",
+		"key_type":                                "key",
+	} {
+		actual := testServer.ReceivedMD[k]
+		require.Len(t, actual, 1)
+		require.Equal(t, v, actual[0])
+	}
+	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 }
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -4,7 +4,6 @@
 	"bytes"
 	"context"
 	"testing"
-	"time"
 
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
@@ -13,7 +12,7 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
 )
 
@@ -29,13 +28,25 @@
 	userId := "1"
 	repo := "group/repo"
 
+	env := sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: "git-upload-pack " + repo,
+		RemoteAddr:      "127.0.0.1",
+	}
+
+	args := &commandargs.Shell{
+		GitlabKeyId: userId,
+		CommandType: commandargs.UploadPack,
+		SshArgs:     []string{"git-upload-pack", repo},
+		Env:         env,
+	}
+
 	cmd := &Command{
 		Config:     &config.Config{GitlabUrl: url},
-		Args:       &commandargs.Shell{GitlabKeyId: userId, CommandType: commandargs.UploadPack, SshArgs: []string{"git-upload-pack", repo}},
+		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
 
-	hook := testhelper.SetupLogger()
 	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
@@ -43,25 +54,20 @@
 	require.NoError(t, err)
 
 	require.Equal(t, "UploadPack: "+repo, output.String())
-	require.Eventually(t, func() bool {
-		entries := hook.AllEntries()
-
-		require.Equal(t, 2, len(entries))
-		require.Contains(t, entries[1].Message, "executing git command")
-		require.Contains(t, entries[1].Message, "command=git-upload-pack")
-		require.Contains(t, entries[1].Message, "gl_key_type=key")
-		require.Contains(t, entries[1].Message, "gl_key_id=123")
-		require.Contains(t, entries[1].Message, "correlation_id=a-correlation-id")
-		return true
-	}, time.Second, time.Millisecond)
 
 	for k, v := range map[string]string{
 		"gitaly-feature-cache_invalidator":        "true",
 		"gitaly-feature-inforef_uploadpack_cache": "false",
+		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-pack",
+		"key_id":                                  "123",
+		"user_id":                                 "1",
+		"remote_ip":                               "127.0.0.1",
+		"key_type":                                "key",
 	} {
 		actual := testServer.ReceivedMD[k]
 		require.Len(t, actual, 1)
 		require.Equal(t, v, actual[0])
 	}
 	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 }
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -9,11 +9,13 @@
 	"path/filepath"
 	"strings"
 	"sync"
+	"time"
 
-	"gitlab.com/gitlab-org/labkit/tracing"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
 	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
 
 const (
@@ -22,8 +24,8 @@
 )
 
 var (
-       defaultHgBinPath string = "hg"
-       defaultHgrcPath  string = "/opt/gitlab/etc/docker.hgrc:/etc/gitlab/heptapod.hgrc"
+	defaultHgBinPath string = "hg"
+	defaultHgrcPath  string = "/opt/gitlab/etc/docker.hgrc:/etc/gitlab/heptapod.hgrc"
 )
 
 type ServerConfig struct {
@@ -31,6 +33,9 @@
 	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
 	WebListen               string   `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
+	GracePeriodSeconds      uint64   `yaml:"grace_period"`
+	ReadinessProbe          string   `yaml:"readiness_probe"`
+	LivenessProbe           string   `yaml:"liveness_probe"`
 	HostKeyFiles            []string `yaml:"host_key_files,omitempty"`
 }
 
@@ -76,10 +81,11 @@
 	Secret         string             `yaml:"secret"`
 	SslCertDir     string             `yaml:"ssl_cert_dir"`
 	HttpSettings   HttpSettingsConfig `yaml:"http_settings"`
-	Mercurial             MercurialConfig    `yaml:"mercurial"`
+	Mercurial      MercurialConfig    `yaml:"mercurial"`
 	Server         ServerConfig       `yaml:"sshd"`
 
 	httpClient     *client.HttpClient
+	httpClientErr  error
 	httpClientOnce sync.Once
 }
 
@@ -87,7 +93,7 @@
 var (
 	DefaultConfig = Config{
 		LogFile:   "gitlab-shell.log",
-		LogFormat: "text",
+		LogFormat: "json",
 		Server:    DefaultServerConfig,
 		User:      "git",
 	}
@@ -96,6 +102,9 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
+		GracePeriodSeconds:      10,
+		ReadinessProbe:          "/start",
+		LivenessProbe:           "/health",
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
@@ -104,30 +113,39 @@
 	}
 )
 
+func (sc *ServerConfig) GracePeriod() time.Duration {
+	return time.Duration(sc.GracePeriodSeconds) * time.Second
+}
+
 func (c *Config) ApplyGlobalState() {
 	if c.SslCertDir != "" {
 		os.Setenv("SSL_CERT_DIR", c.SslCertDir)
 	}
 }
 
-func (c *Config) HttpClient() *client.HttpClient {
+func (c *Config) HttpClient() (*client.HttpClient, error) {
 	c.httpClientOnce.Do(func() {
-		client := client.NewHTTPClient(
+		client, err := client.NewHTTPClientWithOpts(
 			c.GitlabUrl,
 			c.GitlabRelativeURLRoot,
 			c.HttpSettings.CaFile,
 			c.HttpSettings.CaPath,
 			c.HttpSettings.SelfSignedCert,
 			c.HttpSettings.ReadTimeoutSeconds,
+			nil,
 		)
+		if err != nil {
+			c.httpClientErr = err
+			return
+		}
 
 		tr := client.Transport
-		client.Transport = tracing.NewRoundTripper(tr)
+		client.Transport = promhttp.InstrumentRoundTripperDuration(metrics.HttpRequestDuration, tr)
 
 		c.httpClient = client
 	})
 
-	return c.httpClient
+	return c.httpClient, c.httpClientErr
 }
 
 // NewFromDirExternal returns a new config from a given root dir. It also applies defaults appropriate for
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -4,8 +4,10 @@
 	"os"
 	"testing"
 
+	"github.com/prometheus/client_golang/prometheus"
 	"github.com/stretchr/testify/require"
 
+	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
 
@@ -22,3 +24,28 @@
 
 	require.Equal(t, "foo", os.Getenv("SSL_CERT_DIR"))
 }
+
+func TestHttpClient(t *testing.T) {
+	url := testserver.StartHttpServer(t, []testserver.TestRequestHandler{})
+
+	config := &Config{GitlabUrl: url}
+	client, err := config.HttpClient()
+	require.NoError(t, err)
+
+	_, err = client.Get("http://host.com/path")
+	require.NoError(t, err)
+
+	ms, err := prometheus.DefaultGatherer.Gather()
+	require.NoError(t, err)
+
+	lastMetric := ms[0]
+	require.Equal(t, lastMetric.GetName(), "gitlab_shell_http_request_seconds")
+
+	labels := lastMetric.GetMetric()[0].Label
+
+	require.Equal(t, "code", labels[0].GetName())
+	require.Equal(t, "404", labels[0].GetValue())
+
+	require.Equal(t, "method", labels[1].GetName())
+	require.Equal(t, "get", labels[1].GetValue())
+}
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -163,9 +163,7 @@
 }
 
 func setup(t *testing.T, allowedPayload string) *Client {
-	testDirCleanup, err := testhelper.PrepareTestRootDir()
-	require.NoError(t, err)
-	defer testDirCleanup()
+	testhelper.PrepareTestRootDir(t)
 
 	body, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
 	require.NoError(t, err)
diff --git a/internal/gitlabnet/accessverifier/hg_client_test.go b/internal/gitlabnet/accessverifier/hg_client_test.go
--- a/internal/gitlabnet/accessverifier/hg_client_test.go
+++ b/internal/gitlabnet/accessverifier/hg_client_test.go
@@ -111,9 +111,7 @@
 }
 
 func hgSetup(t *testing.T) *HgClient {
-	testDirCleanup, err := testhelper.PrepareTestRootDir()
-	require.NoError(t, err)
-	defer testDirCleanup()
+	testhelper.PrepareTestRootDir(t)
 
 	body, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "responses/hg_allowed.json"))
 	require.NoError(t, err)
diff --git a/internal/gitlabnet/client.go b/internal/gitlabnet/client.go
--- a/internal/gitlabnet/client.go
+++ b/internal/gitlabnet/client.go
@@ -15,7 +15,10 @@
 )
 
 func GetClient(config *config.Config) (*client.GitlabNetClient, error) {
-	httpClient := config.HttpClient()
+	httpClient, err := config.HttpClient()
+	if err != nil {
+		return nil, err
+	}
 
 	if httpClient == nil {
 		return nil, fmt.Errorf("Unsupported protocol")
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -8,18 +8,19 @@
 
 	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
 	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
-	log "github.com/sirupsen/logrus"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/metadata"
 
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+
 	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
+	"gitlab.com/gitlab-org/labkit/log"
 	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
 )
 
@@ -75,7 +76,6 @@
 func (gc *GitalyCommand) LogExecution(ctx context.Context, repository *pb.Repository, response *accessverifier.Response, env sshenv.Env) {
 	fields := log.Fields{
 		"command":         gc.ServiceName,
-		"correlation_id":  correlation.ExtractFromContext(ctx),
 		"gl_project_path": repository.GlProjectPath,
 		"gl_repository":   repository.GlRepository,
 		"user_id":         response.UserId,
@@ -86,7 +86,7 @@
 		"gl_key_id":       response.KeyId,
 	}
 
-	log.WithFields(fields).Info("executing git command")
+	log.WithContextFields(ctx, fields).Info("executing git command")
 }
 
 func withOutgoingMetadata(ctx context.Context, features map[string]string) context.Context {
@@ -108,9 +108,9 @@
 
 	serviceName := correlation.ExtractClientNameFromContext(ctx)
 	if serviceName == "" {
-		log.Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
+		serviceName = "gitlab-shell-unknown"
 
-		serviceName = "gitlab-shell-unknown"
+		log.WithFields(log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
 	}
 
 	serviceName = fmt.Sprintf("%s-%s", serviceName, gc.ServiceName)
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -2,25 +2,54 @@
 
 import (
 	"fmt"
+	"io"
 	"io/ioutil"
 	"log/syslog"
 	"os"
+	"time"
 
-	log "github.com/sirupsen/logrus"
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
-func configureLogFormat(cfg *config.Config) {
-	if cfg.LogFormat == "json" {
-		log.SetFormatter(&log.JSONFormatter{})
+func logFmt(inFmt string) string {
+	// Hide the "combined" format, since that makes no sense in gitlab-shell.
+	// The default is JSON when unspecified.
+	if inFmt == "" || inFmt == "combined" {
+		return "json"
+	}
+
+	return inFmt
+}
+
+func logFile(inFile string) string {
+	if inFile == "" {
+		return "stderr"
+	}
+
+	return inFile
+}
+
+func buildOpts(cfg *config.Config) []log.LoggerOption {
+	return []log.LoggerOption{
+		log.WithFormatter(logFmt(cfg.LogFormat)),
+		log.WithOutputName(logFile(cfg.LogFile)),
+		log.WithTimezone(time.UTC),
 	}
 }
 
 // Configure configures the logging singleton for operation inside a remote TTY (like SSH). In this
 // mode an empty LogFile is not accepted and syslog is used as a fallback when LogFile could not be
 // opened for writing.
-func Configure(cfg *config.Config) {
-	logFile, err := os.OpenFile(cfg.LogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
+func Configure(cfg *config.Config) io.Closer {
+	var closer io.Closer = ioutil.NopCloser(nil)
+	err := fmt.Errorf("No logfile specified")
+
+	if cfg.LogFile != "" {
+		closer, err = log.Initialize(buildOpts(cfg)...)
+	}
+
 	if err != nil {
 		progName, _ := os.Executable()
 		syslogLogger, syslogLoggerErr := syslog.NewLogger(syslog.LOG_ERR|syslog.LOG_USER, 0)
@@ -32,29 +61,35 @@
 			fmt.Fprintf(os.Stderr, msg)
 		}
 
-		// Discard logs since a log file was specified but couldn't be opened
-		log.SetOutput(ioutil.Discard)
+		cfg.LogFile = "/dev/null"
+		closer, err = log.Initialize(buildOpts(cfg)...)
+		if err != nil {
+			log.WithError(err).Warn("Unable to configure logging to /dev/null, leaving unconfigured")
+		}
 	}
 
-	log.SetOutput(logFile)
-
-	configureLogFormat(cfg)
+	return closer
 }
 
 // ConfigureStandalone configures the logging singleton for standalone operation. In this mode an
-// empty LogFile is treated as logging to standard output and standard output is used as a fallback
+// empty LogFile is treated as logging to stderr, and standard output is used as a fallback
 // when LogFile could not be opened for writing.
-func ConfigureStandalone(cfg *config.Config) {
-	if cfg.LogFile == "" {
-		return
+func ConfigureStandalone(cfg *config.Config) io.Closer {
+	closer, err1 := log.Initialize(buildOpts(cfg)...)
+	if err1 != nil {
+		var err2 error
+
+		cfg.LogFile = "stdout"
+		closer, err2 = log.Initialize(buildOpts(cfg)...)
+
+		// Output this after the logger has been configured!
+		log.WithError(err1).WithField("log_file", cfg.LogFile).Warn("Unable to configure logging, falling back to STDOUT")
+
+		// LabKit v1.7.0 doesn't have any conditions where logging to "stdout" will fail
+		if err2 != nil {
+			log.WithError(err2).Warn("Unable to configure logging to STDOUT, leaving unconfigured")
+		}
 	}
 
-	logFile, err := os.OpenFile(cfg.LogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
-	if err != nil {
-		log.Printf("Unable to configure logging, falling back to stdout: %v", err)
-		return
-	}
-	log.SetOutput(logFile)
-
-	configureLogFormat(cfg)
+	return closer
 }
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -3,11 +3,13 @@
 import (
 	"io/ioutil"
 	"os"
+	"regexp"
 	"strings"
 	"testing"
 
-	log "github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
@@ -21,7 +23,9 @@
 		LogFormat: "json",
 	}
 
-	Configure(&config)
+	closer := Configure(&config)
+	defer closer.Close()
+
 	log.Info("this is a test")
 
 	tmpFile.Close()
@@ -41,6 +45,34 @@
 		LogFormat: "json",
 	}
 
-	Configure(&config)
+	closer := Configure(&config)
+	defer closer.Close()
+
 	log.Info("this is a test")
 }
+
+func TestLogInUTC(t *testing.T) {
+	tmpFile, err := ioutil.TempFile(os.TempDir(), "logtest-")
+	require.NoError(t, err)
+	defer tmpFile.Close()
+	defer os.Remove(tmpFile.Name())
+
+	config := config.Config{
+		LogFile:   tmpFile.Name(),
+		LogFormat: "json",
+	}
+
+	closer := Configure(&config)
+	defer closer.Close()
+
+	log.Info("this is a test")
+
+	data, err := ioutil.ReadFile(tmpFile.Name())
+	require.NoError(t, err)
+
+	utc := `[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z`
+	r, e := regexp.MatchString(utc, string(data))
+
+	require.NoError(t, e)
+	require.True(t, r)
+}
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
new file mode 100644
--- /dev/null
+++ b/internal/metrics/metrics.go
@@ -0,0 +1,63 @@
+package metrics
+
+import (
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promauto"
+)
+
+const (
+	namespace     = "gitlab_shell"
+	sshdSubsystem = "sshd"
+	httpSubsystem = "http"
+)
+
+var (
+	SshdConnectionDuration = promauto.NewHistogram(
+		prometheus.HistogramOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      "connection_duration_seconds",
+			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
+			Buckets: []float64{
+				0.005, /* 5ms */
+				0.025, /* 25ms */
+				0.1,   /* 100ms */
+				0.5,   /* 500ms */
+				1.0,   /* 1s */
+				10.0,  /* 10s */
+				30.0,  /* 30s */
+				60.0,  /* 1m */
+				300.0, /* 5m */
+			},
+		},
+	)
+
+	SshdHitMaxSessions = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      "concurrent_limited_sessions_total",
+			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
+		},
+	)
+
+	HttpRequestDuration = promauto.NewHistogramVec(
+		prometheus.HistogramOpts{
+			Namespace: namespace,
+			Subsystem: httpSubsystem,
+			Name:      "request_seconds",
+			Help:      "A histogram of latencies for gitlab-shell http requests",
+			Buckets: []float64{
+				0.01, /* 10ms */
+				0.05, /* 50ms */
+				0.1,  /* 100ms */
+				0.25, /* 250ms */
+				0.5,  /* 500ms */
+				1.0,  /* 1s */
+				5.0,  /* 5s */
+				10.0, /* 10s */
+			},
+		},
+		[]string{"code", "method"},
+	)
+)
diff --git a/internal/pktline/pktline.go b/internal/pktline/pktline.go
--- a/internal/pktline/pktline.go
+++ b/internal/pktline/pktline.go
@@ -8,6 +8,7 @@
 	"bytes"
 	"fmt"
 	"io"
+	"regexp"
 	"strconv"
 )
 
@@ -16,6 +17,8 @@
 	pktDelim   = "0001"
 )
 
+var branchRemovalPktRegexp = regexp.MustCompile(`\A[a-f0-9]{4}[a-f0-9]{40} 0{40} `)
+
 // NewScanner returns a bufio.Scanner that splits on Git pktline boundaries
 func NewScanner(r io.Reader) *bufio.Scanner {
 	scanner := bufio.NewScanner(r)
@@ -24,7 +27,16 @@
 	return scanner
 }
 
-// IsDone detects the special flush packet '0009done\n'
+func IsRefRemoval(pkt []byte) bool {
+	return branchRemovalPktRegexp.Match(pkt)
+}
+
+// IsFlush detects the special flush packet '0000'
+func IsFlush(pkt []byte) bool {
+	return bytes.Equal(pkt, []byte("0000"))
+}
+
+// IsDone detects the special done packet '0009done\n'
 func IsDone(pkt []byte) bool {
 	return bytes.Equal(pkt, PktDone())
 }
diff --git a/internal/pktline/pktline_test.go b/internal/pktline/pktline_test.go
--- a/internal/pktline/pktline_test.go
+++ b/internal/pktline/pktline_test.go
@@ -68,6 +68,40 @@
 	}
 }
 
+func TestIsRefRemoval(t *testing.T) {
+	testCases := []struct {
+		in        string
+		isRemoval bool
+	}{
+		{in: "003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug", isRemoval: false},
+		{in: "003f0000000000000000000000000000000000000000 7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug", isRemoval: false},
+		{in: "003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 0000000000000000000000000000000000000000 refs/heads/debug", isRemoval: true},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.in, func(t *testing.T) {
+			require.Equal(t, tc.isRemoval, IsRefRemoval([]byte(tc.in)))
+		})
+	}
+}
+
+func TestIsFlush(t *testing.T) {
+	testCases := []struct {
+		in    string
+		flush bool
+	}{
+		{in: "0008abcd", flush: false},
+		{in: "invalid packet", flush: false},
+		{in: "0000", flush: true},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.in, func(t *testing.T) {
+			require.Equal(t, tc.flush, IsFlush([]byte(tc.in)))
+		})
+	}
+}
+
 func TestIsDone(t *testing.T) {
 	testCases := []struct {
 		in   string
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -4,47 +4,12 @@
 	"context"
 	"time"
 
-	"github.com/prometheus/client_golang/prometheus"
-	"github.com/prometheus/client_golang/prometheus/promauto"
-	log "github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
-)
-
-const (
-	namespace     = "gitlab_shell"
-	sshdSubsystem = "sshd"
-)
 
-var (
-	sshdConnectionDuration = promauto.NewHistogram(
-		prometheus.HistogramOpts{
-			Namespace: namespace,
-			Subsystem: sshdSubsystem,
-			Name:      "connection_duration_seconds",
-			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
-			Buckets: []float64{
-				0.005, /* 5ms */
-				0.025, /* 25ms */
-				0.1,   /* 100ms */
-				0.5,   /* 500ms */
-				1.0,   /* 1s */
-				10.0,  /* 10s */
-				30.0,  /* 30s */
-				60.0,  /* 1m */
-				300.0, /* 5m */
-			},
-		},
-	)
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
-	sshdHitMaxSessions = promauto.NewCounter(
-		prometheus.CounterOpts{
-			Namespace: namespace,
-			Subsystem: sshdSubsystem,
-			Name:      "concurrent_limited_sessions_total",
-			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
-		},
-	)
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 type connection struct {
@@ -64,7 +29,7 @@
 }
 
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
-	defer sshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
+	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
 
 	for newChannel := range chans {
 		if newChannel.ChannelType() != "session" {
@@ -73,12 +38,12 @@
 		}
 		if !c.concurrentSessions.TryAcquire(1) {
 			newChannel.Reject(ssh.ResourceShortage, "too many concurrent sessions")
-			sshdHitMaxSessions.Inc()
+			metrics.SshdHitMaxSessions.Inc()
 			continue
 		}
 		channel, requests, err := newChannel.Accept()
 		if err != nil {
-			log.Infof("Could not accept channel: %v", err)
+			log.WithError(err).Info("could not accept channel")
 			c.concurrentSessions.Release(1)
 			continue
 		}
@@ -89,7 +54,7 @@
 			// Prevent a panic in a single session from taking out the whole server
 			defer func() {
 				if err := recover(); err != nil {
-					log.Warnf("panic handling session from %s: recovered: %#+v", c.remoteAddr, err)
+					log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", c.remoteAddr)
 				}
 			}()
 
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -2,6 +2,7 @@
 
 import (
 	"context"
+	"errors"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -9,22 +10,31 @@
 )
 
 type rejectCall struct {
-	reason ssh.RejectionReason
+	reason  ssh.RejectionReason
 	message string
 }
 
 type fakeNewChannel struct {
 	channelType string
 	extraData   []byte
+	acceptErr   error
+
+	acceptCh chan struct{}
 	rejectCh chan rejectCall
 }
 
 func (f *fakeNewChannel) Accept() (ssh.Channel, <-chan *ssh.Request, error) {
-	return nil, nil, nil
+	if f.acceptCh != nil {
+		f.acceptCh <- struct{}{}
+	}
+
+	return nil, nil, f.acceptErr
 }
 
 func (f *fakeNewChannel) Reject(reason ssh.RejectionReason, message string) error {
-	f.rejectCh <- rejectCall{reason: reason, message: message}
+	if f.rejectCh != nil {
+		f.rejectCh <- rejectCall{reason: reason, message: message}
+	}
 
 	return nil
 }
@@ -63,7 +73,9 @@
 }
 
 func TestUnknownChannelType(t *testing.T) {
-	rejectCh := make(chan rejectCall, 1)
+	rejectCh := make(chan rejectCall)
+	defer close(rejectCh)
+
 	newChannel := &fakeNewChannel{channelType: "unknown session", rejectCh: rejectCh}
 	conn, chans := setup(1, newChannel)
 
@@ -72,8 +84,64 @@
 	}()
 
 	rejectionData := <-rejectCh
-	close(rejectCh)
 
 	expectedRejection := rejectCall{reason: ssh.UnknownChannelType, message: "unknown channel type"}
 	require.Equal(t, expectedRejection, rejectionData)
 }
+
+func TestTooManySessions(t *testing.T) {
+	rejectCh := make(chan rejectCall)
+	defer close(rejectCh)
+
+	newChannel := &fakeNewChannel{channelType: "session", rejectCh: rejectCh}
+	conn, chans := setup(1, newChannel)
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	go func() {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+			<-ctx.Done() // Keep the accepted channel open until the end of the test
+		})
+	}()
+
+	chans <- newChannel
+	require.Equal(t, <-rejectCh, rejectCall{reason: ssh.ResourceShortage, message: "too many concurrent sessions"})
+}
+
+func TestAcceptSessionSucceeds(t *testing.T) {
+	newChannel := &fakeNewChannel{channelType: "session"}
+	conn, chans := setup(1, newChannel)
+
+	channelHandled := false
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		channelHandled = true
+		close(chans)
+	})
+
+	require.True(t, channelHandled)
+}
+
+func TestAcceptSessionFails(t *testing.T) {
+	acceptCh := make(chan struct{})
+	defer close(acceptCh)
+
+	acceptErr := errors.New("some failure")
+	newChannel := &fakeNewChannel{channelType: "session", acceptCh: acceptCh, acceptErr: acceptErr}
+	conn, chans := setup(1, newChannel)
+
+	channelHandled := false
+	go func() {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+			channelHandled = true
+		})
+	}()
+
+	require.Equal(t, <-acceptCh, struct{}{})
+
+	// Waits until the number of sessions is back to 0, since we can only have 1
+	conn.concurrentSessions.Acquire(context.Background(), 1)
+	defer conn.concurrentSessions.Release(1)
+
+	require.False(t, channelHandled)
+}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -7,41 +7,171 @@
 	"fmt"
 	"io/ioutil"
 	"net"
+	"net/http"
 	"strconv"
+	"sync"
 	"time"
 
-	log "github.com/sirupsen/logrus"
-
 	"github.com/pires/go-proxyproto"
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
+
 	"gitlab.com/gitlab-org/labkit/correlation"
+	"gitlab.com/gitlab-org/labkit/log"
+)
+
+type status int
+
+const (
+	StatusStarting status = iota
+	StatusReady
+	StatusOnShutdown
+	StatusClosed
+	ProxyHeaderTimeout = 90 * time.Second
 )
 
-func Run(ctx context.Context, cfg *config.Config) error {
+type Server struct {
+	Config *config.Config
+
+	status               status
+	statusMu             sync.Mutex
+	wg                   sync.WaitGroup
+	listener             net.Listener
+	hostKeys             []ssh.Signer
+	authorizedKeysClient *authorizedkeys.Client
+}
+
+func NewServer(cfg *config.Config) (*Server, error) {
 	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
 	if err != nil {
-		return fmt.Errorf("failed to initialize GitLab client: %w", err)
+		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+	}
+
+	var hostKeys []ssh.Signer
+	for _, filename := range cfg.Server.HostKeyFiles {
+		keyRaw, err := ioutil.ReadFile(filename)
+		if err != nil {
+			log.WithError(err).Warnf("Failed to read host key %v", filename)
+			continue
+		}
+		key, err := ssh.ParsePrivateKey(keyRaw)
+		if err != nil {
+			log.WithError(err).Warnf("Failed to parse host key %v", filename)
+			continue
+		}
+
+		hostKeys = append(hostKeys, key)
+	}
+	if len(hostKeys) == 0 {
+		return nil, fmt.Errorf("No host keys could be loaded, aborting")
 	}
 
-	sshListener, err := net.Listen("tcp", cfg.Server.Listen)
+	return &Server{Config: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
+}
+
+func (s *Server) ListenAndServe(ctx context.Context) error {
+	if err := s.listen(); err != nil {
+		return err
+	}
+	defer s.listener.Close()
+
+	s.serve(ctx)
+
+	return nil
+}
+
+func (s *Server) Shutdown() error {
+	if s.listener == nil {
+		return nil
+	}
+
+	s.changeStatus(StatusOnShutdown)
+
+	return s.listener.Close()
+}
+
+func (s *Server) MonitoringServeMux() *http.ServeMux {
+	mux := http.NewServeMux()
+
+	mux.HandleFunc(s.Config.Server.ReadinessProbe, func(w http.ResponseWriter, r *http.Request) {
+		if s.getStatus() == StatusReady {
+			w.WriteHeader(http.StatusOK)
+		} else {
+			w.WriteHeader(http.StatusServiceUnavailable)
+		}
+	})
+
+	mux.HandleFunc(s.Config.Server.LivenessProbe, func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusOK)
+	})
+
+	return mux
+}
+
+func (s *Server) listen() error {
+	sshListener, err := net.Listen("tcp", s.Config.Server.Listen)
 	if err != nil {
 		return fmt.Errorf("failed to listen for connection: %w", err)
 	}
-	if cfg.Server.ProxyProtocol {
-		sshListener = &proxyproto.Listener{Listener: sshListener}
+
+	if s.Config.Server.ProxyProtocol {
+		sshListener = &proxyproto.Listener{
+			Listener:          sshListener,
+			ReadHeaderTimeout: ProxyHeaderTimeout,
+		}
 
 		log.Info("Proxy protocol is enabled")
 	}
-	defer sshListener.Close()
+
+	log.WithFields(log.Fields{"tcp_address": sshListener.Addr().String()}).Info("Listening for SSH connections")
+
+	s.listener = sshListener
+
+	return nil
+}
+
+func (s *Server) serve(ctx context.Context) {
+	s.changeStatus(StatusReady)
+
+	for {
+		nconn, err := s.listener.Accept()
+		if err != nil {
+			if s.getStatus() == StatusOnShutdown {
+				break
+			}
+
+			log.WithError(err).Warn("Failed to accept connection")
+			continue
+		}
 
-	log.Infof("Listening on %v", sshListener.Addr().String())
+		s.wg.Add(1)
+		go s.handleConn(ctx, nconn)
+	}
+
+	s.wg.Wait()
+
+	s.changeStatus(StatusClosed)
+}
 
+func (s *Server) changeStatus(st status) {
+	s.statusMu.Lock()
+	s.status = st
+	s.statusMu.Unlock()
+}
+
+func (s *Server) getStatus() status {
+	s.statusMu.Lock()
+	defer s.statusMu.Unlock()
+
+	return s.status
+}
+
+func (s *Server) serverConfig(ctx context.Context) *ssh.ServerConfig {
 	sshCfg := &ssh.ServerConfig{
 		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
-			if conn.User() != cfg.User {
+			if conn.User() != s.Config.User {
 				return nil, errors.New("unknown user")
 			}
 			if key.Type() == ssh.KeyAlgoDSA {
@@ -49,7 +179,7 @@
 			}
 			ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
 			defer cancel()
-			res, err := authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
+			res, err := s.authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
 			if err != nil {
 				return nil, err
 			}
@@ -63,63 +193,41 @@
 		},
 	}
 
-	var loadedHostKeys uint
-	for _, filename := range cfg.Server.HostKeyFiles {
-		keyRaw, err := ioutil.ReadFile(filename)
-		if err != nil {
-			log.Warnf("Failed to read host key %v: %v", filename, err)
-			continue
-		}
-		key, err := ssh.ParsePrivateKey(keyRaw)
-		if err != nil {
-			log.Warnf("Failed to parse host key %v: %v", filename, err)
-			continue
-		}
-		loadedHostKeys++
+	for _, key := range s.hostKeys {
 		sshCfg.AddHostKey(key)
 	}
-	if loadedHostKeys == 0 {
-		return fmt.Errorf("No host keys could be loaded, aborting")
-	}
 
-	for {
-		nconn, err := sshListener.Accept()
-		if err != nil {
-			log.Warnf("Failed to accept connection: %v\n", err)
-			continue
-		}
-
-		go handleConn(ctx, cfg, sshCfg, nconn)
-	}
+	return sshCfg
 }
 
-func handleConn(ctx context.Context, cfg *config.Config, sshCfg *ssh.ServerConfig, nconn net.Conn) {
+func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
 	remoteAddr := nconn.RemoteAddr().String()
 
+	defer s.wg.Done()
 	defer nconn.Close()
 
 	// Prevent a panic in a single connection from taking out the whole server
 	defer func() {
 		if err := recover(); err != nil {
-			log.Warnf("panic handling connection from %s: recovered: %#+v", remoteAddr, err)
+			log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", remoteAddr)
 		}
 	}()
 
 	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
 	defer cancel()
 
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, sshCfg)
+	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig(ctx))
 	if err != nil {
-		log.Infof("Failed to initialize SSH connection: %v", err)
+		log.WithError(err).Info("Failed to initialize SSH connection")
 		return
 	}
 
 	go ssh.DiscardRequests(reqs)
 
-	conn := newConnection(cfg.Server.ConcurrentSessionsLimit, remoteAddr)
+	conn := newConnection(s.Config.Server.ConcurrentSessionsLimit, remoteAddr)
 	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
 		session := &session{
-			cfg:         cfg,
+			cfg:         s.Config,
 			channel:     channel,
 			gitlabKeyId: sconn.Permissions.Extensions["key-id"],
 			remoteAddr:  remoteAddr,
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/sshd_test.go
@@ -0,0 +1,190 @@
+package sshd
+
+import (
+	"context"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"path"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+)
+
+const (
+	serverUrl = "127.0.0.1:50000"
+	user      = "git"
+)
+
+var (
+	correlationId = ""
+)
+
+func TestListenAndServe(t *testing.T) {
+	s := setupServer(t)
+
+	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.NoError(t, err)
+	defer client.Close()
+
+	require.NoError(t, s.Shutdown())
+	verifyStatus(t, s, StatusOnShutdown)
+
+	holdSession(t, client)
+
+	_, err = ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.Equal(t, err.Error(), "dial tcp 127.0.0.1:50000: connect: connection refused")
+
+	client.Close()
+
+	verifyStatus(t, s, StatusClosed)
+}
+
+func TestCorrelationId(t *testing.T) {
+	setupServer(t)
+
+	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.NoError(t, err)
+	defer client.Close()
+
+	holdSession(t, client)
+
+	previousCorrelationId := correlationId
+
+	client, err = ssh.Dial("tcp", serverUrl, clientConfig(t))
+	require.NoError(t, err)
+	defer client.Close()
+
+	holdSession(t, client)
+
+	require.NotEqual(t, previousCorrelationId, correlationId)
+}
+
+func TestReadinessProbe(t *testing.T) {
+	s := &Server{Config: &config.Config{Server: config.DefaultServerConfig}}
+
+	require.Equal(t, StatusStarting, s.getStatus())
+
+	mux := s.MonitoringServeMux()
+
+	req := httptest.NewRequest("GET", "/start", nil)
+
+	r := httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 503, r.Result().StatusCode)
+
+	s.changeStatus(StatusReady)
+
+	r = httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 200, r.Result().StatusCode)
+
+	s.changeStatus(StatusOnShutdown)
+
+	r = httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 503, r.Result().StatusCode)
+}
+
+func TestLivenessProbe(t *testing.T) {
+	s := &Server{Config: &config.Config{Server: config.DefaultServerConfig}}
+	mux := s.MonitoringServeMux()
+
+	req := httptest.NewRequest("GET", "/health", nil)
+
+	r := httptest.NewRecorder()
+	mux.ServeHTTP(r, req)
+	require.Equal(t, 200, r.Result().StatusCode)
+}
+
+func setupServer(t *testing.T) *Server {
+	t.Helper()
+
+	requests := []testserver.TestRequestHandler{
+		{
+			Path: "/api/v4/internal/authorized_keys",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				correlationId = r.Header.Get("X-Request-Id")
+
+				require.NotEmpty(t, correlationId)
+
+				fmt.Fprint(w, `{"id": 1000, "key": "key"}`)
+			},
+		}, {
+			Path: "/api/v4/internal/discover",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				require.Equal(t, correlationId, r.Header.Get("X-Request-Id"))
+
+				fmt.Fprint(w, `{"id": 1000, "name": "Test User", "username": "test-user"}`)
+			},
+		},
+	}
+
+	testhelper.PrepareTestRootDir(t)
+
+	url := testserver.StartSocketHttpServer(t, requests)
+	srvCfg := config.ServerConfig{
+		Listen:                  serverUrl,
+		ConcurrentSessionsLimit: 1,
+		HostKeyFiles:            []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")},
+	}
+
+	s, err := NewServer(&config.Config{User: user, RootDir: "/tmp", GitlabUrl: url, Server: srvCfg})
+	require.NoError(t, err)
+
+	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
+	t.Cleanup(func() { s.Shutdown() })
+
+	verifyStatus(t, s, StatusReady)
+
+	return s
+}
+
+func clientConfig(t *testing.T) *ssh.ClientConfig {
+	keyRaw, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server_authorized_key"))
+	pKey, _, _, _, err := ssh.ParseAuthorizedKey(keyRaw)
+	require.NoError(t, err)
+
+	key, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/client/key.pem"))
+	require.NoError(t, err)
+	signer, err := ssh.ParsePrivateKey(key)
+	require.NoError(t, err)
+
+	return &ssh.ClientConfig{
+		User: user,
+		Auth: []ssh.AuthMethod{
+			ssh.PublicKeys(signer),
+		},
+		HostKeyCallback: ssh.FixedHostKey(pKey),
+	}
+}
+
+func holdSession(t *testing.T, c *ssh.Client) {
+	session, err := c.NewSession()
+	require.NoError(t, err)
+	defer session.Close()
+
+	output, err := session.Output("discover")
+	require.NoError(t, err)
+	require.Equal(t, "Welcome to GitLab, @test-user!\n", string(output))
+}
+
+func verifyStatus(t *testing.T, s *Server, st status) {
+	for i := 5; i < 500; i += 50 {
+		if s.getStatus() == st {
+			break
+		}
+
+		// Sleep incrementally ~2s in total
+		time.Sleep(time.Duration(i) * time.Millisecond)
+	}
+
+	require.Equal(t, st, s.getStatus())
+}
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -65,10 +65,10 @@
 					"gl_email":        "testgr@heptapod.test",
 					"gl_id":           17,
 				}
-				if (hg_native != nil) {
+				if hg_native != nil {
 					body["hg_native"] = *hg_native
 				}
-				if (no_git != nil) {
+				if no_git != nil {
 					body["no_git"] = *no_git
 				}
 				w.WriteHeader(http.StatusOK)
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server_authorized_key b/internal/testhelper/testdata/testroot/certs/valid/server_authorized_key
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server_authorized_key
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yql
diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go
--- a/internal/testhelper/testhelper.go
+++ b/internal/testhelper/testhelper.go
@@ -6,11 +6,10 @@
 	"os"
 	"path"
 	"runtime"
-	"time"
+	"testing"
 
 	"github.com/otiai10/copy"
-	"github.com/sirupsen/logrus"
-	"github.com/sirupsen/logrus/hooks/test"
+	"github.com/stretchr/testify/require"
 )
 
 var (
@@ -31,42 +30,21 @@
 	}
 }
 
-func PrepareTestRootDir() (func(), error) {
-	if err := os.MkdirAll(TestRoot, 0700); err != nil {
-		return nil, err
-	}
+func PrepareTestRootDir(t *testing.T) {
+	t.Helper()
 
-	var oldWd string
-	cleanup := func() {
-		if oldWd != "" {
-			err := os.Chdir(oldWd)
-			if err != nil {
-				panic(err)
-			}
-		}
+	require.NoError(t, os.MkdirAll(TestRoot, 0700))
 
-		if err := os.RemoveAll(TestRoot); err != nil {
-			panic(err)
-		}
-	}
+	t.Cleanup(func() { os.RemoveAll(TestRoot) })
 
-	if err := copyTestData(); err != nil {
-		cleanup()
-		return nil, err
-	}
+	require.NoError(t, copyTestData())
 
 	oldWd, err := os.Getwd()
-	if err != nil {
-		cleanup()
-		return nil, err
-	}
+	require.NoError(t, err)
 
-	if err := os.Chdir(TestRoot); err != nil {
-		cleanup()
-		return nil, err
-	}
+	t.Cleanup(func() { os.Chdir(oldWd) })
 
-	return cleanup, nil
+	require.NoError(t, os.Chdir(TestRoot))
 }
 
 func copyTestData() error {
@@ -94,25 +72,3 @@
 	err := os.Setenv(key, value)
 	return func() { os.Setenv(key, oldValue) }, err
 }
-
-func SetupLogger() *test.Hook {
-	logger, hook := test.NewNullLogger()
-	logrus.SetOutput(logger.Writer())
-
-	return hook
-}
-
-// logrus fires a Goroutine to write the output log, but there's no way to
-// flush all outstanding hooks to fire. We just wait up to a second
-// for an event to appear.
-func WaitForLogEvent(hook *test.Hook) bool {
-	for i := 0; i < 10; i++ {
-		if entry := hook.LastEntry(); entry != nil {
-			return true
-		}
-
-		time.Sleep(100 * time.Millisecond)
-	}
-
-	return false
-}
diff --git a/spec/gitlab_shell_custom_git_receive_pack_spec.rb b/spec/gitlab_shell_custom_git_receive_pack_spec.rb
--- a/spec/gitlab_shell_custom_git_receive_pack_spec.rb
+++ b/spec/gitlab_shell_custom_git_receive_pack_spec.rb
@@ -74,11 +74,11 @@
           expect(stderr.gets).to eq("remote: message\n")
           expect(stderr.gets).to eq(remote_blank_line)
 
-          stdin.puts("input")
+          stdin.puts("0009input")
           stdin.close
 
           expect(stdout.gets(6)).to eq("custom")
-          expect(stdout.flush.read).to eq("input\n")
+          expect(stdout.flush.read).to eq("0009input")
         end
       end
 
diff --git a/spec/gitlab_shell_discover_spec.rb b/spec/gitlab_shell_discover_spec.rb
--- a/spec/gitlab_shell_discover_spec.rb
+++ b/spec/gitlab_shell_discover_spec.rb
@@ -108,7 +108,7 @@
     end
 
     it 'outputs "Only SSH allowed"' do
-      _, stderr, status = run!(["-c/usr/share/webapps/gitlab-shell/bin/gitlab-shell", "username-someuser"], env: {})
+      _, stderr, status = run!(["-c/usr/share/webapps/gitlab-shell/bin/gitlab-shell", "username-someuser"], env: {'SSH_CONNECTION' => ''})
 
       expect(stderr).to eq("Only SSH allowed\n")
       expect(status).not_to be_success
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1629388460 0
#      Thu Aug 19 15:54:20 2021 +0000
# Node ID 3be8a3ee74158b77c49e7c2619076cf04c6b1a69
# Parent  6415f1b04fa7998f0c06b2a12b06d45eb15651ad
refactor: move away from ioutil (deprecated)

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -5,7 +5,7 @@
 	"encoding/base64"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"path"
 	"strings"
@@ -83,7 +83,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, string(responseBody), "Hello")
 	})
@@ -99,7 +99,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, "Echo: {\"key\":\"value\"}", string(responseBody))
 	})
@@ -155,7 +155,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
@@ -170,7 +170,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
@@ -194,7 +194,7 @@
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				require.Equal(t, http.MethodPost, r.Method)
 
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -6,7 +6,6 @@
 	"crypto/x509"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"os"
@@ -139,7 +138,7 @@
 	}
 
 	if hcc.caPath != "" {
-		fis, _ := ioutil.ReadDir(hcc.caPath)
+		fis, _ := os.ReadDir(hcc.caPath)
 		for _, fi := range fis {
 			if fi.IsDir() {
 				continue
@@ -171,7 +170,7 @@
 }
 
 func addCertToPool(certPool *x509.CertPool, fileName string) {
-	cert, err := ioutil.ReadFile(fileName)
+	cert, err := os.ReadFile(fileName)
 	if err == nil {
 		certPool.AppendCertsFromPEM(cert)
 	}
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -4,7 +4,7 @@
 	"context"
 	"encoding/base64"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strings"
 	"testing"
@@ -64,7 +64,7 @@
 	defer response.Body.Close()
 
 	require.NotNil(t, response)
-	responseBody, err := ioutil.ReadAll(response.Body)
+	responseBody, err := io.ReadAll(response.Body)
 	require.NoError(t, err)
 
 	headerParts := strings.Split(string(responseBody), " ")
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"path"
 	"testing"
@@ -57,7 +57,7 @@
 
 			defer response.Body.Close()
 
-			responseBody, err := ioutil.ReadAll(response.Body)
+			responseBody, err := io.ReadAll(response.Body)
 			require.NoError(t, err)
 			require.Equal(t, string(responseBody), "Hello")
 		})
diff --git a/client/testserver/gitalyserver.go b/client/testserver/gitalyserver.go
--- a/client/testserver/gitalyserver.go
+++ b/client/testserver/gitalyserver.go
@@ -1,7 +1,6 @@
 package testserver
 
 import (
-	"io/ioutil"
 	"net"
 	"os"
 	"path"
@@ -61,7 +60,7 @@
 func StartGitalyServer(t *testing.T) (string, *TestGitalyServer) {
 	t.Helper()
 
-	tempDir, _ := ioutil.TempDir("", "gitlab-shell-test-api")
+	tempDir, _ := os.MkdirTemp("", "gitlab-shell-test-api")
 	gitalySocketPath := path.Join(tempDir, "gitaly.sock")
 	t.Cleanup(func() { os.RemoveAll(tempDir) })
 
diff --git a/client/testserver/testserver.go b/client/testserver/testserver.go
--- a/client/testserver/testserver.go
+++ b/client/testserver/testserver.go
@@ -3,7 +3,7 @@
 import (
 	"crypto/tls"
 	"crypto/x509"
-	"io/ioutil"
+	"io"
 	"log"
 	"net"
 	"net/http"
@@ -18,7 +18,7 @@
 )
 
 var (
-	tempDir, _ = ioutil.TempDir("", "gitlab-shell-test-api")
+	tempDir, _ = os.MkdirTemp("", "gitlab-shell-test-api")
 	testSocket = path.Join(tempDir, "internal.sock")
 )
 
@@ -41,7 +41,7 @@
 		Handler: buildHandler(handlers),
 		// We'll put this server through some nasty stuff we don't want
 		// in our test output
-		ErrorLog: log.New(ioutil.Discard, "", 0),
+		ErrorLog: log.New(io.Discard, "", 0),
 	}
 	go server.Serve(socketListener)
 
@@ -76,7 +76,7 @@
 	server.TLS.BuildNameToCertificate()
 
 	if clientCAPath != "" {
-		caCert, err := ioutil.ReadFile(clientCAPath)
+		caCert, err := os.ReadFile(clientCAPath)
 		require.NoError(t, err)
 
 		caCertPool := x509.NewCertPool()
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -8,7 +8,6 @@
 	"encoding/pem"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"net/http/httptest"
@@ -112,7 +111,7 @@
 		case "/api/v4/internal/two_factor_otp_check":
 			fmt.Fprint(w, `{"success": true}`)
 		case "/api/v4/internal/allowed":
-			body, err := ioutil.ReadFile(filepath.Join(testhelper.TestRoot, "responses/allowed_without_console_messages.json"))
+			body, err := os.ReadFile(filepath.Join(testhelper.TestRoot, "responses/allowed_without_console_messages.json"))
 			require.NoError(t, err)
 
 			response := strings.Replace(string(body), "GITALY_REPOSITORY", testRepo, 1)
@@ -198,7 +197,7 @@
 func configureSSHD(t *testing.T, apiServer string) (string, ed25519.PublicKey) {
 	t.Helper()
 
-	dir, err := ioutil.TempDir("", "gitlab-sshd-acceptance-test-")
+	dir, err := os.MkdirTemp("", "gitlab-sshd-acceptance-test-")
 	require.NoError(t, err)
 	t.Cleanup(func() { os.RemoveAll(dir) })
 
@@ -209,11 +208,11 @@
 	require.NoError(t, err)
 
 	configFileData := genServerConfig(apiServer, hostKeyFile)
-	require.NoError(t, ioutil.WriteFile(configFile, configFileData, 0644))
+	require.NoError(t, os.WriteFile(configFile, configFileData, 0644))
 
 	block := &pem.Block{Type: "OPENSSH PRIVATE KEY", Bytes: edkey.MarshalED25519PrivateKey(priv)}
 	hostKeyData := pem.EncodeToMemory(block)
-	require.NoError(t, ioutil.WriteFile(hostKeyFile, hostKeyData, 0400))
+	require.NoError(t, os.WriteFile(hostKeyFile, hostKeyData, 0400))
 
 	return dir, pub
 }
@@ -326,7 +325,7 @@
 	_, err = fmt.Fprintln(stdin, "yes")
 	require.NoError(t, err)
 
-	output, err := ioutil.ReadAll(stdout)
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 	require.Equal(t, `
 Your two-factor authentication recovery codes are:
@@ -365,7 +364,7 @@
 	_, err = fmt.Fprintln(stdin, "otp123")
 	require.NoError(t, err)
 
-	output, err := ioutil.ReadAll(stdout)
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 	require.Equal(t, "OTP validation successful. Git operations are now allowed.\n", string(output))
 }
diff --git a/internal/command/lfsauthenticate/lfsauthenticate_test.go b/internal/command/lfsauthenticate/lfsauthenticate_test.go
--- a/internal/command/lfsauthenticate/lfsauthenticate_test.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -70,7 +70,7 @@
 		{
 			Path: "/api/v4/internal/lfs_authenticate",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 				require.NoError(t, err)
 
@@ -94,7 +94,7 @@
 		{
 			Path: "/api/v4/internal/allowed",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 				require.NoError(t, err)
 
diff --git a/internal/command/personalaccesstoken/personalaccesstoken_test.go b/internal/command/personalaccesstoken/personalaccesstoken_test.go
--- a/internal/command/personalaccesstoken/personalaccesstoken_test.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -26,7 +26,7 @@
 		{
 			Path: "/api/v4/internal/personal_access_token",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/command/shared/accessverifier/accessverifier_test.go b/internal/command/shared/accessverifier/accessverifier_test.go
--- a/internal/command/shared/accessverifier/accessverifier_test.go
+++ b/internal/command/shared/accessverifier/accessverifier_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -27,7 +27,7 @@
 		{
 			Path: "/api/v4/internal/allowed",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var requestBody *accessverifier.Request
diff --git a/internal/command/shared/customaction/customaction_test.go b/internal/command/shared/customaction/customaction_test.go
--- a/internal/command/shared/customaction/customaction_test.go
+++ b/internal/command/shared/customaction/customaction_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -23,7 +23,7 @@
 		{
 			Path: "/geo/proxy/info_refs_receive_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
@@ -39,7 +39,7 @@
 		{
 			Path: "/geo/proxy/receive_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
@@ -92,7 +92,7 @@
 		{
 			Path: "/geo/proxy/info_refs_upload_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
@@ -108,7 +108,7 @@
 		{
 			Path: "/geo/proxy/upload_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
diff --git a/internal/command/twofactorrecover/twofactorrecover_test.go b/internal/command/twofactorrecover/twofactorrecover_test.go
--- a/internal/command/twofactorrecover/twofactorrecover_test.go
+++ b/internal/command/twofactorrecover/twofactorrecover_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strings"
 	"testing"
@@ -27,7 +27,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_recovery_codes",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/command/twofactorverify/twofactorverify_test.go b/internal/command/twofactorverify/twofactorverify_test.go
--- a/internal/command/twofactorverify/twofactorverify_test.go
+++ b/internal/command/twofactorverify/twofactorverify_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -22,7 +22,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_otp_check",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -2,7 +2,6 @@
 
 import (
 	"errors"
-	"io/ioutil"
 	"net/url"
 	"os"
 	"path"
@@ -147,7 +146,7 @@
 	*cfg = DefaultConfig
 	cfg.RootDir = filepath.Dir(path)
 
-	configBytes, err := ioutil.ReadFile(path)
+	configBytes, err := os.ReadFile(path)
 	if err != nil {
 		return nil, err
 	}
@@ -191,7 +190,7 @@
 		cfg.SecretFilePath = path.Join(cfg.RootDir, cfg.SecretFilePath)
 	}
 
-	secretFileContent, err := ioutil.ReadFile(cfg.SecretFilePath)
+	secretFileContent, err := os.ReadFile(cfg.SecretFilePath)
 	if err != nil {
 		return err
 	}
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -3,8 +3,9 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
+	"os"
 	"path"
 	"testing"
 
@@ -165,14 +166,14 @@
 func setup(t *testing.T, allowedPayload string) *Client {
 	testhelper.PrepareTestRootDir(t)
 
-	body, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
+	body, err := os.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
 	require.NoError(t, err)
 
 	var bodyWithPayload []byte
 
 	if allowedPayload != "" {
 		allowedWithPayloadPath := path.Join(testhelper.TestRoot, allowedPayload)
-		bodyWithPayload, err = ioutil.ReadFile(allowedWithPayloadPath)
+		bodyWithPayload, err = os.ReadFile(allowedWithPayloadPath)
 		require.NoError(t, err)
 	}
 
@@ -180,7 +181,7 @@
 		{
 			Path: "/api/v4/internal/allowed",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var requestBody *Request
diff --git a/internal/gitlabnet/lfsauthenticate/client_test.go b/internal/gitlabnet/lfsauthenticate/client_test.go
--- a/internal/gitlabnet/lfsauthenticate/client_test.go
+++ b/internal/gitlabnet/lfsauthenticate/client_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -24,7 +24,7 @@
 		{
 			Path: "/api/v4/internal/lfs_authenticate",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 				require.NoError(t, err)
 
diff --git a/internal/gitlabnet/personalaccesstoken/client_test.go b/internal/gitlabnet/personalaccesstoken/client_test.go
--- a/internal/gitlabnet/personalaccesstoken/client_test.go
+++ b/internal/gitlabnet/personalaccesstoken/client_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -24,7 +24,7 @@
 		{
 			Path: "/api/v4/internal/personal_access_token",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/gitlabnet/twofactorrecover/client_test.go b/internal/gitlabnet/twofactorrecover/client_test.go
--- a/internal/gitlabnet/twofactorrecover/client_test.go
+++ b/internal/gitlabnet/twofactorrecover/client_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -24,7 +24,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_recovery_codes",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/gitlabnet/twofactorverify/client_test.go b/internal/gitlabnet/twofactorverify/client_test.go
--- a/internal/gitlabnet/twofactorverify/client_test.go
+++ b/internal/gitlabnet/twofactorverify/client_test.go
@@ -3,11 +3,12 @@
 import (
 	"context"
 	"encoding/json"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
@@ -20,7 +21,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_otp_check",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -3,7 +3,6 @@
 import (
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log/syslog"
 	"os"
 	"time"
@@ -43,7 +42,7 @@
 // mode an empty LogFile is not accepted and syslog is used as a fallback when LogFile could not be
 // opened for writing.
 func Configure(cfg *config.Config) io.Closer {
-	var closer io.Closer = ioutil.NopCloser(nil)
+	var closer io.Closer = io.NopCloser(nil)
 	err := fmt.Errorf("No logfile specified")
 
 	if cfg.LogFile != "" {
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -1,7 +1,6 @@
 package logger
 
 import (
-	"io/ioutil"
 	"os"
 	"regexp"
 	"strings"
@@ -14,7 +13,7 @@
 )
 
 func TestConfigure(t *testing.T) {
-	tmpFile, err := ioutil.TempFile(os.TempDir(), "logtest-")
+	tmpFile, err := os.CreateTemp(os.TempDir(), "logtest-")
 	require.NoError(t, err)
 	defer tmpFile.Close()
 
@@ -30,13 +29,13 @@
 
 	tmpFile.Close()
 
-	data, err := ioutil.ReadFile(tmpFile.Name())
+	data, err := os.ReadFile(tmpFile.Name())
 	require.NoError(t, err)
 	require.True(t, strings.Contains(string(data), `msg":"this is a test"`))
 }
 
 func TestConfigureWithPermissionError(t *testing.T) {
-	tmpPath, err := ioutil.TempDir(os.TempDir(), "logtest-")
+	tmpPath, err := os.MkdirTemp(os.TempDir(), "logtest-")
 	require.NoError(t, err)
 	defer os.RemoveAll(tmpPath)
 
@@ -52,7 +51,7 @@
 }
 
 func TestLogInUTC(t *testing.T) {
-	tmpFile, err := ioutil.TempFile(os.TempDir(), "logtest-")
+	tmpFile, err := os.CreateTemp(os.TempDir(), "logtest-")
 	require.NoError(t, err)
 	defer tmpFile.Close()
 	defer os.Remove(tmpFile.Name())
@@ -67,7 +66,7 @@
 
 	log.Info("this is a test")
 
-	data, err := ioutil.ReadFile(tmpFile.Name())
+	data, err := os.ReadFile(tmpFile.Name())
 	require.NoError(t, err)
 
 	utc := `[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z`
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -5,9 +5,9 @@
 	"encoding/base64"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"net"
 	"net/http"
+	"os"
 	"strconv"
 	"sync"
 	"time"
@@ -51,7 +51,7 @@
 
 	var hostKeys []ssh.Signer
 	for _, filename := range cfg.Server.HostKeyFiles {
-		keyRaw, err := ioutil.ReadFile(filename)
+		keyRaw, err := os.ReadFile(filename)
 		if err != nil {
 			log.WithError(err).Warnf("Failed to read host key %v", filename)
 			continue
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -3,9 +3,9 @@
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
+	"os"
 	"path"
 	"testing"
 	"time"
@@ -148,11 +148,11 @@
 }
 
 func clientConfig(t *testing.T) *ssh.ClientConfig {
-	keyRaw, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server_authorized_key"))
+	keyRaw, err := os.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server_authorized_key"))
 	pKey, _, _, _, err := ssh.ParseAuthorizedKey(keyRaw)
 	require.NoError(t, err)
 
-	key, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/client/key.pem"))
+	key, err := os.ReadFile(path.Join(testhelper.TestRoot, "certs/client/key.pem"))
 	require.NoError(t, err)
 	signer, err := ssh.ParsePrivateKey(key)
 	require.NoError(t, err)
diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go
--- a/internal/testhelper/testhelper.go
+++ b/internal/testhelper/testhelper.go
@@ -2,7 +2,6 @@
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path"
 	"runtime"
@@ -13,7 +12,7 @@
 )
 
 var (
-	TestRoot, _ = ioutil.TempDir("", "test-gitlab-shell")
+	TestRoot, _ = os.MkdirTemp("", "test-gitlab-shell")
 )
 
 func TempEnv(env map[string]string) func() {
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1631091173 0
#      Wed Sep 08 08:52:53 2021 +0000
# Node ID 8e875d8f6c8521570db68de53e522902911ff494
# Parent  854da4efdb45866ea0259ffcd156c46f5f8b238b
# Parent  3be8a3ee74158b77c49e7c2619076cf04c6b1a69
Merge branch 'remove/ioutil' into 'main'

refactor: move away from ioutil (deprecated)

See merge request gitlab-org/gitlab-shell!511

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -5,7 +5,7 @@
 	"encoding/base64"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"path"
 	"strings"
@@ -83,7 +83,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, string(responseBody), "Hello")
 	})
@@ -99,7 +99,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, "Echo: {\"key\":\"value\"}", string(responseBody))
 	})
@@ -155,7 +155,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
@@ -170,7 +170,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
@@ -194,7 +194,7 @@
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				require.Equal(t, http.MethodPost, r.Method)
 
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -6,7 +6,6 @@
 	"crypto/x509"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"os"
@@ -139,7 +138,7 @@
 	}
 
 	if hcc.caPath != "" {
-		fis, _ := ioutil.ReadDir(hcc.caPath)
+		fis, _ := os.ReadDir(hcc.caPath)
 		for _, fi := range fis {
 			if fi.IsDir() {
 				continue
@@ -171,7 +170,7 @@
 }
 
 func addCertToPool(certPool *x509.CertPool, fileName string) {
-	cert, err := ioutil.ReadFile(fileName)
+	cert, err := os.ReadFile(fileName)
 	if err == nil {
 		certPool.AppendCertsFromPEM(cert)
 	}
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -4,7 +4,7 @@
 	"context"
 	"encoding/base64"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strings"
 	"testing"
@@ -64,7 +64,7 @@
 	defer response.Body.Close()
 
 	require.NotNil(t, response)
-	responseBody, err := ioutil.ReadAll(response.Body)
+	responseBody, err := io.ReadAll(response.Body)
 	require.NoError(t, err)
 
 	headerParts := strings.Split(string(responseBody), " ")
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"path"
 	"testing"
@@ -57,7 +57,7 @@
 
 			defer response.Body.Close()
 
-			responseBody, err := ioutil.ReadAll(response.Body)
+			responseBody, err := io.ReadAll(response.Body)
 			require.NoError(t, err)
 			require.Equal(t, string(responseBody), "Hello")
 		})
diff --git a/client/testserver/gitalyserver.go b/client/testserver/gitalyserver.go
--- a/client/testserver/gitalyserver.go
+++ b/client/testserver/gitalyserver.go
@@ -1,7 +1,6 @@
 package testserver
 
 import (
-	"io/ioutil"
 	"net"
 	"os"
 	"path"
@@ -61,7 +60,7 @@
 func StartGitalyServer(t *testing.T) (string, *TestGitalyServer) {
 	t.Helper()
 
-	tempDir, _ := ioutil.TempDir("", "gitlab-shell-test-api")
+	tempDir, _ := os.MkdirTemp("", "gitlab-shell-test-api")
 	gitalySocketPath := path.Join(tempDir, "gitaly.sock")
 	t.Cleanup(func() { os.RemoveAll(tempDir) })
 
diff --git a/client/testserver/testserver.go b/client/testserver/testserver.go
--- a/client/testserver/testserver.go
+++ b/client/testserver/testserver.go
@@ -3,7 +3,7 @@
 import (
 	"crypto/tls"
 	"crypto/x509"
-	"io/ioutil"
+	"io"
 	"log"
 	"net"
 	"net/http"
@@ -18,7 +18,7 @@
 )
 
 var (
-	tempDir, _ = ioutil.TempDir("", "gitlab-shell-test-api")
+	tempDir, _ = os.MkdirTemp("", "gitlab-shell-test-api")
 	testSocket = path.Join(tempDir, "internal.sock")
 )
 
@@ -41,7 +41,7 @@
 		Handler: buildHandler(handlers),
 		// We'll put this server through some nasty stuff we don't want
 		// in our test output
-		ErrorLog: log.New(ioutil.Discard, "", 0),
+		ErrorLog: log.New(io.Discard, "", 0),
 	}
 	go server.Serve(socketListener)
 
@@ -76,7 +76,7 @@
 	server.TLS.BuildNameToCertificate()
 
 	if clientCAPath != "" {
-		caCert, err := ioutil.ReadFile(clientCAPath)
+		caCert, err := os.ReadFile(clientCAPath)
 		require.NoError(t, err)
 
 		caCertPool := x509.NewCertPool()
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -8,7 +8,6 @@
 	"encoding/pem"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"net/http/httptest"
@@ -112,7 +111,7 @@
 		case "/api/v4/internal/two_factor_otp_check":
 			fmt.Fprint(w, `{"success": true}`)
 		case "/api/v4/internal/allowed":
-			body, err := ioutil.ReadFile(filepath.Join(testhelper.TestRoot, "responses/allowed_without_console_messages.json"))
+			body, err := os.ReadFile(filepath.Join(testhelper.TestRoot, "responses/allowed_without_console_messages.json"))
 			require.NoError(t, err)
 
 			response := strings.Replace(string(body), "GITALY_REPOSITORY", testRepo, 1)
@@ -198,7 +197,7 @@
 func configureSSHD(t *testing.T, apiServer string) (string, ed25519.PublicKey) {
 	t.Helper()
 
-	dir, err := ioutil.TempDir("", "gitlab-sshd-acceptance-test-")
+	dir, err := os.MkdirTemp("", "gitlab-sshd-acceptance-test-")
 	require.NoError(t, err)
 	t.Cleanup(func() { os.RemoveAll(dir) })
 
@@ -209,11 +208,11 @@
 	require.NoError(t, err)
 
 	configFileData := genServerConfig(apiServer, hostKeyFile)
-	require.NoError(t, ioutil.WriteFile(configFile, configFileData, 0644))
+	require.NoError(t, os.WriteFile(configFile, configFileData, 0644))
 
 	block := &pem.Block{Type: "OPENSSH PRIVATE KEY", Bytes: edkey.MarshalED25519PrivateKey(priv)}
 	hostKeyData := pem.EncodeToMemory(block)
-	require.NoError(t, ioutil.WriteFile(hostKeyFile, hostKeyData, 0400))
+	require.NoError(t, os.WriteFile(hostKeyFile, hostKeyData, 0400))
 
 	return dir, pub
 }
@@ -326,7 +325,7 @@
 	_, err = fmt.Fprintln(stdin, "yes")
 	require.NoError(t, err)
 
-	output, err := ioutil.ReadAll(stdout)
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 	require.Equal(t, `
 Your two-factor authentication recovery codes are:
@@ -365,7 +364,7 @@
 	_, err = fmt.Fprintln(stdin, "otp123")
 	require.NoError(t, err)
 
-	output, err := ioutil.ReadAll(stdout)
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 	require.Equal(t, "OTP validation successful. Git operations are now allowed.\n", string(output))
 }
diff --git a/internal/command/lfsauthenticate/lfsauthenticate_test.go b/internal/command/lfsauthenticate/lfsauthenticate_test.go
--- a/internal/command/lfsauthenticate/lfsauthenticate_test.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -70,7 +70,7 @@
 		{
 			Path: "/api/v4/internal/lfs_authenticate",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 				require.NoError(t, err)
 
@@ -94,7 +94,7 @@
 		{
 			Path: "/api/v4/internal/allowed",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 				require.NoError(t, err)
 
diff --git a/internal/command/personalaccesstoken/personalaccesstoken_test.go b/internal/command/personalaccesstoken/personalaccesstoken_test.go
--- a/internal/command/personalaccesstoken/personalaccesstoken_test.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -26,7 +26,7 @@
 		{
 			Path: "/api/v4/internal/personal_access_token",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/command/shared/accessverifier/accessverifier_test.go b/internal/command/shared/accessverifier/accessverifier_test.go
--- a/internal/command/shared/accessverifier/accessverifier_test.go
+++ b/internal/command/shared/accessverifier/accessverifier_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -27,7 +27,7 @@
 		{
 			Path: "/api/v4/internal/allowed",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var requestBody *accessverifier.Request
diff --git a/internal/command/shared/customaction/customaction_test.go b/internal/command/shared/customaction/customaction_test.go
--- a/internal/command/shared/customaction/customaction_test.go
+++ b/internal/command/shared/customaction/customaction_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -23,7 +23,7 @@
 		{
 			Path: "/geo/proxy/info_refs_receive_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
@@ -39,7 +39,7 @@
 		{
 			Path: "/geo/proxy/receive_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
@@ -92,7 +92,7 @@
 		{
 			Path: "/geo/proxy/info_refs_upload_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
@@ -108,7 +108,7 @@
 		{
 			Path: "/geo/proxy/upload_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
diff --git a/internal/command/twofactorrecover/twofactorrecover_test.go b/internal/command/twofactorrecover/twofactorrecover_test.go
--- a/internal/command/twofactorrecover/twofactorrecover_test.go
+++ b/internal/command/twofactorrecover/twofactorrecover_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strings"
 	"testing"
@@ -27,7 +27,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_recovery_codes",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/command/twofactorverify/twofactorverify_test.go b/internal/command/twofactorverify/twofactorverify_test.go
--- a/internal/command/twofactorverify/twofactorverify_test.go
+++ b/internal/command/twofactorverify/twofactorverify_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -22,7 +22,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_otp_check",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -2,7 +2,6 @@
 
 import (
 	"errors"
-	"io/ioutil"
 	"net/url"
 	"os"
 	"path"
@@ -147,7 +146,7 @@
 	*cfg = DefaultConfig
 	cfg.RootDir = filepath.Dir(path)
 
-	configBytes, err := ioutil.ReadFile(path)
+	configBytes, err := os.ReadFile(path)
 	if err != nil {
 		return nil, err
 	}
@@ -191,7 +190,7 @@
 		cfg.SecretFilePath = path.Join(cfg.RootDir, cfg.SecretFilePath)
 	}
 
-	secretFileContent, err := ioutil.ReadFile(cfg.SecretFilePath)
+	secretFileContent, err := os.ReadFile(cfg.SecretFilePath)
 	if err != nil {
 		return err
 	}
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -3,8 +3,9 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
+	"os"
 	"path"
 	"testing"
 
@@ -165,14 +166,14 @@
 func setup(t *testing.T, allowedPayload string) *Client {
 	testhelper.PrepareTestRootDir(t)
 
-	body, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
+	body, err := os.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
 	require.NoError(t, err)
 
 	var bodyWithPayload []byte
 
 	if allowedPayload != "" {
 		allowedWithPayloadPath := path.Join(testhelper.TestRoot, allowedPayload)
-		bodyWithPayload, err = ioutil.ReadFile(allowedWithPayloadPath)
+		bodyWithPayload, err = os.ReadFile(allowedWithPayloadPath)
 		require.NoError(t, err)
 	}
 
@@ -180,7 +181,7 @@
 		{
 			Path: "/api/v4/internal/allowed",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var requestBody *Request
diff --git a/internal/gitlabnet/lfsauthenticate/client_test.go b/internal/gitlabnet/lfsauthenticate/client_test.go
--- a/internal/gitlabnet/lfsauthenticate/client_test.go
+++ b/internal/gitlabnet/lfsauthenticate/client_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -24,7 +24,7 @@
 		{
 			Path: "/api/v4/internal/lfs_authenticate",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 				require.NoError(t, err)
 
diff --git a/internal/gitlabnet/personalaccesstoken/client_test.go b/internal/gitlabnet/personalaccesstoken/client_test.go
--- a/internal/gitlabnet/personalaccesstoken/client_test.go
+++ b/internal/gitlabnet/personalaccesstoken/client_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -24,7 +24,7 @@
 		{
 			Path: "/api/v4/internal/personal_access_token",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/gitlabnet/twofactorrecover/client_test.go b/internal/gitlabnet/twofactorrecover/client_test.go
--- a/internal/gitlabnet/twofactorrecover/client_test.go
+++ b/internal/gitlabnet/twofactorrecover/client_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -24,7 +24,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_recovery_codes",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/gitlabnet/twofactorverify/client_test.go b/internal/gitlabnet/twofactorverify/client_test.go
--- a/internal/gitlabnet/twofactorverify/client_test.go
+++ b/internal/gitlabnet/twofactorverify/client_test.go
@@ -3,11 +3,12 @@
 import (
 	"context"
 	"encoding/json"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
@@ -20,7 +21,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_otp_check",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -3,7 +3,6 @@
 import (
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log/syslog"
 	"os"
 	"time"
@@ -43,7 +42,7 @@
 // mode an empty LogFile is not accepted and syslog is used as a fallback when LogFile could not be
 // opened for writing.
 func Configure(cfg *config.Config) io.Closer {
-	var closer io.Closer = ioutil.NopCloser(nil)
+	var closer io.Closer = io.NopCloser(nil)
 	err := fmt.Errorf("No logfile specified")
 
 	if cfg.LogFile != "" {
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -1,7 +1,6 @@
 package logger
 
 import (
-	"io/ioutil"
 	"os"
 	"regexp"
 	"strings"
@@ -14,7 +13,7 @@
 )
 
 func TestConfigure(t *testing.T) {
-	tmpFile, err := ioutil.TempFile(os.TempDir(), "logtest-")
+	tmpFile, err := os.CreateTemp(os.TempDir(), "logtest-")
 	require.NoError(t, err)
 	defer tmpFile.Close()
 
@@ -30,13 +29,13 @@
 
 	tmpFile.Close()
 
-	data, err := ioutil.ReadFile(tmpFile.Name())
+	data, err := os.ReadFile(tmpFile.Name())
 	require.NoError(t, err)
 	require.True(t, strings.Contains(string(data), `msg":"this is a test"`))
 }
 
 func TestConfigureWithPermissionError(t *testing.T) {
-	tmpPath, err := ioutil.TempDir(os.TempDir(), "logtest-")
+	tmpPath, err := os.MkdirTemp(os.TempDir(), "logtest-")
 	require.NoError(t, err)
 	defer os.RemoveAll(tmpPath)
 
@@ -52,7 +51,7 @@
 }
 
 func TestLogInUTC(t *testing.T) {
-	tmpFile, err := ioutil.TempFile(os.TempDir(), "logtest-")
+	tmpFile, err := os.CreateTemp(os.TempDir(), "logtest-")
 	require.NoError(t, err)
 	defer tmpFile.Close()
 	defer os.Remove(tmpFile.Name())
@@ -67,7 +66,7 @@
 
 	log.Info("this is a test")
 
-	data, err := ioutil.ReadFile(tmpFile.Name())
+	data, err := os.ReadFile(tmpFile.Name())
 	require.NoError(t, err)
 
 	utc := `[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z`
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -5,9 +5,9 @@
 	"encoding/base64"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"net"
 	"net/http"
+	"os"
 	"strconv"
 	"sync"
 	"time"
@@ -51,7 +51,7 @@
 
 	var hostKeys []ssh.Signer
 	for _, filename := range cfg.Server.HostKeyFiles {
-		keyRaw, err := ioutil.ReadFile(filename)
+		keyRaw, err := os.ReadFile(filename)
 		if err != nil {
 			log.WithError(err).Warnf("Failed to read host key %v", filename)
 			continue
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -3,9 +3,9 @@
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
+	"os"
 	"path"
 	"testing"
 	"time"
@@ -148,11 +148,11 @@
 }
 
 func clientConfig(t *testing.T) *ssh.ClientConfig {
-	keyRaw, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server_authorized_key"))
+	keyRaw, err := os.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server_authorized_key"))
 	pKey, _, _, _, err := ssh.ParseAuthorizedKey(keyRaw)
 	require.NoError(t, err)
 
-	key, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/client/key.pem"))
+	key, err := os.ReadFile(path.Join(testhelper.TestRoot, "certs/client/key.pem"))
 	require.NoError(t, err)
 	signer, err := ssh.ParsePrivateKey(key)
 	require.NoError(t, err)
diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go
--- a/internal/testhelper/testhelper.go
+++ b/internal/testhelper/testhelper.go
@@ -2,7 +2,6 @@
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path"
 	"runtime"
@@ -13,7 +12,7 @@
 )
 
 var (
-	TestRoot, _ = ioutil.TempDir("", "test-gitlab-shell")
+	TestRoot, _ = os.MkdirTemp("", "test-gitlab-shell")
 )
 
 func TempEnv(env map[string]string) func() {
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1628702942 0
#      Wed Aug 11 17:29:02 2021 +0000
# Node ID cdc409eddfe2feb8c498280977addb799ffd6ccc
# Parent  6ea629933069190232b9adb008f60e84d889904f
refactor: remove commandargs.GenericArgs

diff --git a/internal/command/commandargs/command_args.go b/internal/command/commandargs/command_args.go
--- a/internal/command/commandargs/command_args.go
+++ b/internal/command/commandargs/command_args.go
@@ -1,6 +1,8 @@
 package commandargs
 
 import (
+	"errors"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
@@ -13,7 +15,7 @@
 }
 
 func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (CommandArgs, error) {
-	var args CommandArgs = &GenericArgs{Arguments: arguments}
+	var args CommandArgs
 
 	switch e.Name {
 	case executable.GitlabShell:
@@ -22,6 +24,10 @@
 		args = &AuthorizedKeys{Arguments: arguments}
 	case executable.AuthorizedPrincipalsCheck:
 		args = &AuthorizedPrincipals{Arguments: arguments}
+	case executable.Healthcheck:
+		return args, nil
+	default:
+		return nil, errors.New("unknown executable")
 	}
 
 	if err := args.Parse(); err != nil {
diff --git a/internal/command/commandargs/command_args_test.go b/internal/command/commandargs/command_args_test.go
--- a/internal/command/commandargs/command_args_test.go
+++ b/internal/command/commandargs/command_args_test.go
@@ -16,6 +16,7 @@
 		env          sshenv.Env
 		arguments    []string
 		expectedArgs CommandArgs
+		expectError  bool
 	}{
 		{
 			desc:         "It sets discover as the command when the command string was empty",
@@ -100,10 +101,10 @@
 			arguments:    []string{"key", "principal-1", "principal-2"},
 			expectedArgs: &AuthorizedPrincipals{Arguments: []string{"key", "principal-1", "principal-2"}, KeyId: "key", Principals: []string{"principal-1", "principal-2"}},
 		}, {
-			desc:         "Unknown executable",
-			executable:   &executable.Executable{Name: "unknown"},
-			arguments:    []string{},
-			expectedArgs: &GenericArgs{Arguments: []string{}},
+			desc:        "Unknown executable",
+			executable:  &executable.Executable{Name: "unknown"},
+			arguments:   []string{},
+			expectError: true,
 		},
 	}
 
@@ -111,8 +112,12 @@
 		t.Run(tc.desc, func(t *testing.T) {
 			result, err := Parse(tc.executable, tc.arguments, tc.env)
 
-			require.NoError(t, err)
-			require.Equal(t, tc.expectedArgs, result)
+			if !tc.expectError {
+				require.NoError(t, err)
+				require.Equal(t, tc.expectedArgs, result)
+			} else {
+				require.Error(t, err)
+			}
 		})
 	}
 }
diff --git a/internal/command/commandargs/generic_args.go b/internal/command/commandargs/generic_args.go
deleted file mode 100644
--- a/internal/command/commandargs/generic_args.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package commandargs
-
-type GenericArgs struct {
-	Arguments []string
-}
-
-func (b *GenericArgs) Parse() error {
-	// Do nothing
-	return nil
-}
-
-func (b *GenericArgs) GetArguments() []string {
-	return b.Arguments
-}
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1631090876 0
#      Wed Sep 08 08:47:56 2021 +0000
# Node ID c1dab100d7cd2d71f31ada539338bf9f11a33a6e
# Parent  cdc409eddfe2feb8c498280977addb799ffd6ccc
refactor: improve unknown executable error message

diff --git a/internal/command/commandargs/command_args.go b/internal/command/commandargs/command_args.go
--- a/internal/command/commandargs/command_args.go
+++ b/internal/command/commandargs/command_args.go
@@ -2,6 +2,7 @@
 
 import (
 	"errors"
+	"fmt"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
@@ -27,7 +28,7 @@
 	case executable.Healthcheck:
 		return args, nil
 	default:
-		return nil, errors.New("unknown executable")
+		return nil, errors.New(fmt.Sprintf("unknown executable: %s", e.Name))
 	}
 
 	if err := args.Parse(); err != nil {
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1631094884 0
#      Wed Sep 08 09:54:44 2021 +0000
# Node ID b1dda74d86534db7944861f5a004347c3f974644
# Parent  c1dab100d7cd2d71f31ada539338bf9f11a33a6e
refactor: add acceptargs field to executable

parse logic will only run if the executable accept args.
healthcheck is the only one not accepting arguments.

diff --git a/cmd/check/main.go b/cmd/check/main.go
--- a/cmd/check/main.go
+++ b/cmd/check/main.go
@@ -19,7 +19,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.Healthcheck)
+	executable, err := executable.New(executable.Healthcheck, false)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go
--- a/cmd/gitlab-shell-authorized-keys-check/main.go
+++ b/cmd/gitlab-shell-authorized-keys-check/main.go
@@ -20,7 +20,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.AuthorizedKeysCheck)
+	executable, err := executable.New(executable.AuthorizedKeysCheck, true)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go
--- a/cmd/gitlab-shell-authorized-principals-check/main.go
+++ b/cmd/gitlab-shell-authorized-principals-check/main.go
@@ -20,7 +20,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.AuthorizedPrincipalsCheck)
+	executable, err := executable.New(executable.AuthorizedPrincipalsCheck, true)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -34,7 +34,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.GitlabShell)
+	executable, err := executable.New(executable.GitlabShell, true)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
diff --git a/internal/command/command.go b/internal/command/command.go
--- a/internal/command/command.go
+++ b/internal/command/command.go
@@ -29,9 +29,13 @@
 }
 
 func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (Command, error) {
-	args, err := commandargs.Parse(e, arguments, env)
-	if err != nil {
-		return nil, err
+	var args commandargs.CommandArgs
+	if e.AcceptArgs {
+		var err error
+		args, err = commandargs.Parse(e, arguments, env)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	if cmd := buildCommand(e, args, config, readWriter); cmd != nil {
diff --git a/internal/command/command_test.go b/internal/command/command_test.go
--- a/internal/command/command_test.go
+++ b/internal/command/command_test.go
@@ -26,10 +26,10 @@
 )
 
 var (
-	authorizedKeysExec       = &executable.Executable{Name: executable.AuthorizedKeysCheck}
-	authorizedPrincipalsExec = &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}
-	checkExec                = &executable.Executable{Name: executable.Healthcheck}
-	gitlabShellExec          = &executable.Executable{Name: executable.GitlabShell}
+	authorizedKeysExec       = &executable.Executable{Name: executable.AuthorizedKeysCheck, AcceptArgs: true}
+	authorizedPrincipalsExec = &executable.Executable{Name: executable.AuthorizedPrincipalsCheck, AcceptArgs: true}
+	checkExec                = &executable.Executable{Name: executable.Healthcheck, AcceptArgs: false}
+	gitlabShellExec          = &executable.Executable{Name: executable.GitlabShell, AcceptArgs: true}
 
 	basicConfig    = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
 	advancedConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket", SslCertDir: "/tmp/certs"}
diff --git a/internal/command/commandargs/command_args.go b/internal/command/commandargs/command_args.go
--- a/internal/command/commandargs/command_args.go
+++ b/internal/command/commandargs/command_args.go
@@ -25,8 +25,6 @@
 		args = &AuthorizedKeys{Arguments: arguments}
 	case executable.AuthorizedPrincipalsCheck:
 		args = &AuthorizedPrincipals{Arguments: arguments}
-	case executable.Healthcheck:
-		return args, nil
 	default:
 		return nil, errors.New(fmt.Sprintf("unknown executable: %s", e.Name))
 	}
diff --git a/internal/executable/executable.go b/internal/executable/executable.go
--- a/internal/executable/executable.go
+++ b/internal/executable/executable.go
@@ -14,8 +14,9 @@
 )
 
 type Executable struct {
-	Name    string
-	RootDir string
+	Name       string
+	RootDir    string
+	AcceptArgs bool
 }
 
 var (
@@ -23,7 +24,7 @@
 	osExecutable = os.Executable
 )
 
-func New(name string) (*Executable, error) {
+func New(name string, acceptArgs bool) (*Executable, error) {
 	path, err := osExecutable()
 	if err != nil {
 		return nil, err
@@ -35,8 +36,9 @@
 	}
 
 	executable := &Executable{
-		Name:    name,
-		RootDir: rootDir,
+		Name:       name,
+		RootDir:    rootDir,
+		AcceptArgs: acceptArgs,
 	}
 
 	return executable, nil
diff --git a/internal/executable/executable_test.go b/internal/executable/executable_test.go
--- a/internal/executable/executable_test.go
+++ b/internal/executable/executable_test.go
@@ -59,7 +59,7 @@
 			fake.Setup()
 			defer fake.Cleanup()
 
-			result, err := New("gitlab-shell")
+			result, err := New("gitlab-shell", true)
 
 			require.NoError(t, err)
 			require.Equal(t, result.Name, "gitlab-shell")
@@ -96,7 +96,7 @@
 			fake.Setup()
 			defer fake.Cleanup()
 
-			_, err := New("gitlab-shell")
+			_, err := New("gitlab-shell", true)
 
 			require.Error(t, err)
 		})
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1631095566 0
#      Wed Sep 08 10:06:06 2021 +0000
# Node ID 9d6099095d58735db722bcca90bc4782f7614ba5
# Parent  8e875d8f6c8521570db68de53e522902911ff494
# Parent  b1dda74d86534db7944861f5a004347c3f974644
Merge branch 'remove/generic-args' into 'main'

refactor: remove commandargs.GenericArgs

Closes #212

See merge request gitlab-org/gitlab-shell!506

diff --git a/cmd/check/main.go b/cmd/check/main.go
--- a/cmd/check/main.go
+++ b/cmd/check/main.go
@@ -19,7 +19,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.Healthcheck)
+	executable, err := executable.New(executable.Healthcheck, false)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go
--- a/cmd/gitlab-shell-authorized-keys-check/main.go
+++ b/cmd/gitlab-shell-authorized-keys-check/main.go
@@ -20,7 +20,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.AuthorizedKeysCheck)
+	executable, err := executable.New(executable.AuthorizedKeysCheck, true)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go
--- a/cmd/gitlab-shell-authorized-principals-check/main.go
+++ b/cmd/gitlab-shell-authorized-principals-check/main.go
@@ -20,7 +20,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.AuthorizedPrincipalsCheck)
+	executable, err := executable.New(executable.AuthorizedPrincipalsCheck, true)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -34,7 +34,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.GitlabShell)
+	executable, err := executable.New(executable.GitlabShell, true)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
diff --git a/internal/command/command.go b/internal/command/command.go
--- a/internal/command/command.go
+++ b/internal/command/command.go
@@ -29,9 +29,13 @@
 }
 
 func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (Command, error) {
-	args, err := commandargs.Parse(e, arguments, env)
-	if err != nil {
-		return nil, err
+	var args commandargs.CommandArgs
+	if e.AcceptArgs {
+		var err error
+		args, err = commandargs.Parse(e, arguments, env)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	if cmd := buildCommand(e, args, config, readWriter); cmd != nil {
diff --git a/internal/command/command_test.go b/internal/command/command_test.go
--- a/internal/command/command_test.go
+++ b/internal/command/command_test.go
@@ -26,10 +26,10 @@
 )
 
 var (
-	authorizedKeysExec       = &executable.Executable{Name: executable.AuthorizedKeysCheck}
-	authorizedPrincipalsExec = &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}
-	checkExec                = &executable.Executable{Name: executable.Healthcheck}
-	gitlabShellExec          = &executable.Executable{Name: executable.GitlabShell}
+	authorizedKeysExec       = &executable.Executable{Name: executable.AuthorizedKeysCheck, AcceptArgs: true}
+	authorizedPrincipalsExec = &executable.Executable{Name: executable.AuthorizedPrincipalsCheck, AcceptArgs: true}
+	checkExec                = &executable.Executable{Name: executable.Healthcheck, AcceptArgs: false}
+	gitlabShellExec          = &executable.Executable{Name: executable.GitlabShell, AcceptArgs: true}
 
 	basicConfig    = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
 	advancedConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket", SslCertDir: "/tmp/certs"}
diff --git a/internal/command/commandargs/command_args.go b/internal/command/commandargs/command_args.go
--- a/internal/command/commandargs/command_args.go
+++ b/internal/command/commandargs/command_args.go
@@ -1,6 +1,9 @@
 package commandargs
 
 import (
+	"errors"
+	"fmt"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
@@ -13,7 +16,7 @@
 }
 
 func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (CommandArgs, error) {
-	var args CommandArgs = &GenericArgs{Arguments: arguments}
+	var args CommandArgs
 
 	switch e.Name {
 	case executable.GitlabShell:
@@ -22,6 +25,8 @@
 		args = &AuthorizedKeys{Arguments: arguments}
 	case executable.AuthorizedPrincipalsCheck:
 		args = &AuthorizedPrincipals{Arguments: arguments}
+	default:
+		return nil, errors.New(fmt.Sprintf("unknown executable: %s", e.Name))
 	}
 
 	if err := args.Parse(); err != nil {
diff --git a/internal/command/commandargs/command_args_test.go b/internal/command/commandargs/command_args_test.go
--- a/internal/command/commandargs/command_args_test.go
+++ b/internal/command/commandargs/command_args_test.go
@@ -16,6 +16,7 @@
 		env          sshenv.Env
 		arguments    []string
 		expectedArgs CommandArgs
+		expectError  bool
 	}{
 		{
 			desc:         "It sets discover as the command when the command string was empty",
@@ -100,10 +101,10 @@
 			arguments:    []string{"key", "principal-1", "principal-2"},
 			expectedArgs: &AuthorizedPrincipals{Arguments: []string{"key", "principal-1", "principal-2"}, KeyId: "key", Principals: []string{"principal-1", "principal-2"}},
 		}, {
-			desc:         "Unknown executable",
-			executable:   &executable.Executable{Name: "unknown"},
-			arguments:    []string{},
-			expectedArgs: &GenericArgs{Arguments: []string{}},
+			desc:        "Unknown executable",
+			executable:  &executable.Executable{Name: "unknown"},
+			arguments:   []string{},
+			expectError: true,
 		},
 	}
 
@@ -111,8 +112,12 @@
 		t.Run(tc.desc, func(t *testing.T) {
 			result, err := Parse(tc.executable, tc.arguments, tc.env)
 
-			require.NoError(t, err)
-			require.Equal(t, tc.expectedArgs, result)
+			if !tc.expectError {
+				require.NoError(t, err)
+				require.Equal(t, tc.expectedArgs, result)
+			} else {
+				require.Error(t, err)
+			}
 		})
 	}
 }
diff --git a/internal/command/commandargs/generic_args.go b/internal/command/commandargs/generic_args.go
deleted file mode 100644
--- a/internal/command/commandargs/generic_args.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package commandargs
-
-type GenericArgs struct {
-	Arguments []string
-}
-
-func (b *GenericArgs) Parse() error {
-	// Do nothing
-	return nil
-}
-
-func (b *GenericArgs) GetArguments() []string {
-	return b.Arguments
-}
diff --git a/internal/executable/executable.go b/internal/executable/executable.go
--- a/internal/executable/executable.go
+++ b/internal/executable/executable.go
@@ -14,8 +14,9 @@
 )
 
 type Executable struct {
-	Name    string
-	RootDir string
+	Name       string
+	RootDir    string
+	AcceptArgs bool
 }
 
 var (
@@ -23,7 +24,7 @@
 	osExecutable = os.Executable
 )
 
-func New(name string) (*Executable, error) {
+func New(name string, acceptArgs bool) (*Executable, error) {
 	path, err := osExecutable()
 	if err != nil {
 		return nil, err
@@ -35,8 +36,9 @@
 	}
 
 	executable := &Executable{
-		Name:    name,
-		RootDir: rootDir,
+		Name:       name,
+		RootDir:    rootDir,
+		AcceptArgs: acceptArgs,
 	}
 
 	return executable, nil
diff --git a/internal/executable/executable_test.go b/internal/executable/executable_test.go
--- a/internal/executable/executable_test.go
+++ b/internal/executable/executable_test.go
@@ -59,7 +59,7 @@
 			fake.Setup()
 			defer fake.Cleanup()
 
-			result, err := New("gitlab-shell")
+			result, err := New("gitlab-shell", true)
 
 			require.NoError(t, err)
 			require.Equal(t, result.Name, "gitlab-shell")
@@ -96,7 +96,7 @@
 			fake.Setup()
 			defer fake.Cleanup()
 
-			_, err := New("gitlab-shell")
+			_, err := New("gitlab-shell", true)
 
 			require.Error(t, err)
 		})
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1631020085 0
#      Tue Sep 07 13:08:05 2021 +0000
# Node ID 092da560442d8510301acabbfc6b1eb16fbc07f0
# Parent  854da4efdb45866ea0259ffcd156c46f5f8b238b
build: move build task to the top of the Makefile

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -8,6 +8,8 @@
 
 PREFIX ?= /usr/local
 
+build: bin/gitlab-shell
+
 validate: verify test
 
 verify: verify_golang
@@ -40,7 +42,6 @@
 _script_install:
 	bin/install
 
-build: bin/gitlab-shell
 compile: bin/gitlab-shell
 bin/gitlab-shell: $(GO_SOURCES)
 	GOBIN="$(CURDIR)/bin" go install $(GOBUILD_FLAGS) ./cmd/...
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1631176434 0
#      Thu Sep 09 08:33:54 2021 +0000
# Node ID 2ba81bca2e86125681be34ca3395651d245b553c
# Parent  9d6099095d58735db722bcca90bc4782f7614ba5
# Parent  092da560442d8510301acabbfc6b1eb16fbc07f0
Merge branch 'build/make' into 'main'

build: move build task to the top of the Makefile

See merge request gitlab-org/gitlab-shell!513

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -8,6 +8,8 @@
 
 PREFIX ?= /usr/local
 
+build: bin/gitlab-shell
+
 validate: verify test
 
 verify: verify_golang
@@ -40,7 +42,6 @@
 _script_install:
 	bin/install
 
-build: bin/gitlab-shell
 compile: bin/gitlab-shell
 bin/gitlab-shell: $(GO_SOURCES)
 	GOBIN="$(CURDIR)/bin" go install $(GOBUILD_FLAGS) ./cmd/...
# HG changeset patch
# User Evan Read <eread@gitlab.com>
# Date 1631576374 -36000
#      Tue Sep 14 09:39:34 2021 +1000
# Node ID 495d526457d639d0e82a9b50ed8145520e9fea42
# Parent  2ba81bca2e86125681be34ca3395651d245b553c
Update Ruby version to 2.7.4 and add Go version 1.16.8 for tooling

diff --git a/.ruby-version b/.ruby-version
--- a/.ruby-version
+++ b/.ruby-version
@@ -1,1 +1,1 @@
-2.7.2
+2.7.4
diff --git a/.tool-versions b/.tool-versions
new file mode 100644
--- /dev/null
+++ b/.tool-versions
@@ -0,0 +1,2 @@
+ruby 2.7.4
+go 1.16.8
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1631578269 0
#      Tue Sep 14 00:11:09 2021 +0000
# Node ID c70a9087948342444e87b77b0fb4a749bec95b42
# Parent  2ba81bca2e86125681be34ca3395651d245b553c
# Parent  495d526457d639d0e82a9b50ed8145520e9fea42
Merge branch 'eread/update-project-tool-versions' into 'main'

Update Ruby version to 2.7.4 and add Go version 1.16.8 for tooling

See merge request gitlab-org/gitlab-shell!517

diff --git a/.ruby-version b/.ruby-version
--- a/.ruby-version
+++ b/.ruby-version
@@ -1,1 +1,1 @@
-2.7.2
+2.7.4
diff --git a/.tool-versions b/.tool-versions
new file mode 100644
--- /dev/null
+++ b/.tool-versions
@@ -0,0 +1,2 @@
+ruby 2.7.4
+go 1.16.8
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1631626218 -3600
#      Tue Sep 14 14:30:18 2021 +0100
# Node ID eaec6774ef2e610eb3399b296d21cd7652b9b8ff
# Parent  c70a9087948342444e87b77b0fb4a749bec95b42
Fix a flaky test

diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -32,7 +32,7 @@
 	client, err := config.HttpClient()
 	require.NoError(t, err)
 
-	_, err = client.Get("http://host.com/path")
+	_, err = client.Get(url)
 	require.NoError(t, err)
 
 	ms, err := prometheus.DefaultGatherer.Gather()
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1631626707 0
#      Tue Sep 14 13:38:27 2021 +0000
# Node ID a6e55095129693b9c697f000caa234685cdc2ba1
# Parent  c70a9087948342444e87b77b0fb4a749bec95b42
# Parent  eaec6774ef2e610eb3399b296d21cd7652b9b8ff
Merge branch 'fix-bad-spec' into 'main'

Fix a flaky test

See merge request gitlab-org/gitlab-shell!519

diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -32,7 +32,7 @@
 	client, err := config.HttpClient()
 	require.NoError(t, err)
 
-	_, err = client.Get("http://host.com/path")
+	_, err = client.Get(url)
 	require.NoError(t, err)
 
 	ms, err := prometheus.DefaultGatherer.Gather()
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1631112035 0
#      Wed Sep 08 14:40:35 2021 +0000
# Node ID a99f16501b1facae51eb16a109177c695102bc96
# Parent  9d6099095d58735db722bcca90bc4782f7614ba5
refactor: rearchitect command and executable Go modules

diff --git a/cmd/check/command/command.go b/cmd/check/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/check/command/command.go
@@ -0,0 +1,22 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+)
+
+
+func New(config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	if cmd := build(config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func build(config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	return &healthcheck.Command{Config: config, ReadWriter: readWriter}
+}
diff --git a/cmd/check/command/command_test.go b/cmd/check/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/check/command/command_test.go
@@ -0,0 +1,42 @@
+package command_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	basicConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a Healthcheck command",
+			config:       basicConfig,
+			expectedType: &healthcheck.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := command.New(tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
diff --git a/cmd/check/main.go b/cmd/check/main.go
--- a/cmd/check/main.go
+++ b/cmd/check/main.go
@@ -4,12 +4,12 @@
 	"fmt"
 	"os"
 
+	checkCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
 func main() {
@@ -19,7 +19,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.Healthcheck, false)
+	executable, err := executable.New(executable.Healthcheck)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
@@ -34,7 +34,7 @@
 	logCloser := logger.Configure(config)
 	defer logCloser.Close()
 
-	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
+	cmd, err := checkCmd.New(config, readWriter)
 	if err != nil {
 		fmt.Fprintf(readWriter.ErrOut, "%v\n", err)
 		os.Exit(1)
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command.go b/cmd/gitlab-shell-authorized-keys-check/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command.go
@@ -0,0 +1,39 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(e, arguments, env)
+	if err != nil {
+		return nil, err
+	}
+
+	if cmd := build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (*commandargs.AuthorizedKeys, error) {
+	args := &commandargs.AuthorizedKeys{Arguments: arguments}
+
+	if err := args.Parse(); err != nil {
+		return nil, err
+	}
+
+	return args, nil
+}
+
+func build(args *commandargs.AuthorizedKeys, config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	return &authorizedkeys.Command{Config: config, Args: args, ReadWriter: readWriter}
+}
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command_test.go b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
@@ -0,0 +1,120 @@
+package command_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	authorizedKeysExec = &executable.Executable{Name: executable.AuthorizedKeysCheck}
+	basicConfig        = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a AuthorizedKeys command",
+			executable:   authorizedKeysExec,
+			arguments:    []string{"git", "git", "key"},
+			config:       basicConfig,
+			expectedType: &authorizedkeys.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := command.New(tc.executable, tc.arguments, tc.env, tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
+
+func TestParseSuccess(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		expectedArgs commandargs.CommandArgs
+		expectError  bool
+	}{
+		{
+			desc:         "It parses authorized-keys command",
+			executable:   &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:    []string{"git", "git", "key"},
+			expectedArgs: &commandargs.AuthorizedKeys{Arguments: []string{"git", "git", "key"}, ExpectedUser: "git", ActualUser: "git", Key: "key"},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := command.Parse(tc.executable, tc.arguments, tc.env)
+
+			if !tc.expectError {
+				require.NoError(t, err)
+				require.Equal(t, tc.expectedArgs, result)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
+
+func TestParseFailure(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		arguments     []string
+		expectedError string
+	}{
+		{
+			desc:          "With not enough arguments for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user"},
+			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
+		},
+		{
+			desc:          "With too many arguments for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user", "user", "key", "something-else"},
+			expectedError: "# Insufficient arguments. 4. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
+		},
+		{
+			desc:          "With missing username for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user", "", "key"},
+			expectedError: "# No username provided",
+		},
+		{
+			desc:          "With missing key for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user", "user", ""},
+			expectedError: "# No key provided",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err := command.Parse(tc.executable, tc.arguments, tc.env)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go
--- a/cmd/gitlab-shell-authorized-keys-check/main.go
+++ b/cmd/gitlab-shell-authorized-keys-check/main.go
@@ -4,6 +4,7 @@
 	"fmt"
 	"os"
 
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -20,7 +21,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.AuthorizedKeysCheck, true)
+	executable, err := executable.New(executable.AuthorizedKeysCheck)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
@@ -35,7 +36,7 @@
 	logCloser := logger.Configure(config)
 	defer logCloser.Close()
 
-	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
+	cmd, err := cmd.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
 	if err != nil {
 		// For now this could happen if `SSH_CONNECTION` is not set on
 		// the environment
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command.go b/cmd/gitlab-shell-authorized-principals-check/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command.go
@@ -0,0 +1,39 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(e, arguments, env)
+	if err != nil {
+		return nil, err
+	}
+
+	if cmd := build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (*commandargs.AuthorizedPrincipals, error) {
+	args := &commandargs.AuthorizedPrincipals{Arguments: arguments}
+
+	if err := args.Parse(); err != nil {
+		return nil, err
+	}
+
+	return args, nil
+}
+
+func build(args *commandargs.AuthorizedPrincipals, config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	return &authorizedprincipals.Command{Config: config, Args: args, ReadWriter: readWriter}
+}
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command_test.go b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
@@ -0,0 +1,114 @@
+package command_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	authorizedPrincipalsExec = &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}
+	basicConfig              = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a AuthorizedPrincipals command",
+			executable:   authorizedPrincipalsExec,
+			arguments:    []string{"key", "principal"},
+			config:       basicConfig,
+			expectedType: &authorizedprincipals.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := cmd.New(tc.executable, tc.arguments, tc.env, tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
+
+func TestParseSuccess(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		expectedArgs commandargs.CommandArgs
+		expectError  bool
+	}{
+		{
+			desc:         "It parses authorized-principals command",
+			executable:   &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:    []string{"key", "principal-1", "principal-2"},
+			expectedArgs: &commandargs.AuthorizedPrincipals{Arguments: []string{"key", "principal-1", "principal-2"}, KeyId: "key", Principals: []string{"principal-1", "principal-2"}},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := cmd.Parse(tc.executable, tc.arguments, tc.env)
+
+			if !tc.expectError {
+				require.NoError(t, err)
+				require.Equal(t, tc.expectedArgs, result)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
+
+func TestParseFailure(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		arguments     []string
+		expectedError string
+	}{
+		{
+			desc:          "With not enough arguments for the AuthorizedPrincipalsCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:     []string{"key"},
+			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-principals-check <key-id> <principal1> [<principal2>...]",
+		},
+		{
+			desc:          "With missing key_id for the AuthorizedPrincipalsCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:     []string{"", "principal"},
+			expectedError: "# No key_id provided",
+		},
+		{
+			desc:          "With blank principal for the AuthorizedPrincipalsCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:     []string{"key", "principal", ""},
+			expectedError: "# An invalid principal was provided",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err := cmd.Parse(tc.executable, tc.arguments, tc.env)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go
--- a/cmd/gitlab-shell-authorized-principals-check/main.go
+++ b/cmd/gitlab-shell-authorized-principals-check/main.go
@@ -4,6 +4,7 @@
 	"fmt"
 	"os"
 
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -20,7 +21,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.AuthorizedPrincipalsCheck, true)
+	executable, err := executable.New(executable.AuthorizedPrincipalsCheck)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
@@ -35,7 +36,7 @@
 	logCloser := logger.Configure(config)
 	defer logCloser.Close()
 
-	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
+	cmd, err := cmd.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
 	if err != nil {
 		// For now this could happen if `SSH_CONNECTION` is not set on
 		// the environment
diff --git a/cmd/gitlab-shell/command/command.go b/cmd/gitlab-shell/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell/command/command.go
@@ -0,0 +1,65 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(e, arguments, env)
+	if err != nil {
+		return nil, err
+	}
+
+	if cmd := Build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (*commandargs.Shell, error) {
+	args := &commandargs.Shell{Arguments: arguments, Env: env}
+
+	if err := args.Parse(); err != nil {
+		return nil, err
+	}
+
+	return args, nil
+}
+
+func Build(args *commandargs.Shell, config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	switch args.CommandType {
+	case commandargs.Discover:
+		return &discover.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.TwoFactorRecover:
+		return &twofactorrecover.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.TwoFactorVerify:
+		return &twofactorverify.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.LfsAuthenticate:
+		return &lfsauthenticate.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.ReceivePack:
+		return &receivepack.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.UploadPack:
+		return &uploadpack.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.UploadArchive:
+		return &uploadarchive.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.PersonalAccessToken:
+		return &personalaccesstoken.Command{Config: config, Args: args, ReadWriter: readWriter}
+	}
+
+	return nil
+}
diff --git a/cmd/gitlab-shell/command/command_test.go b/cmd/gitlab-shell/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell/command/command_test.go
@@ -0,0 +1,281 @@
+package command_test
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	gitlabShellExec = &executable.Executable{Name: executable.GitlabShell}
+	basicConfig     = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a Discover command",
+			executable:   gitlabShellExec,
+			env:          buildEnv(""),
+			config:       basicConfig,
+			expectedType: &discover.Command{},
+		},
+		{
+			desc:         "it returns a TwoFactorRecover command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("2fa_recovery_codes"),
+			config:       basicConfig,
+			expectedType: &twofactorrecover.Command{},
+		},
+		{
+			desc:         "it returns a TwoFactorVerify command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("2fa_verify"),
+			config:       basicConfig,
+			expectedType: &twofactorverify.Command{},
+		},
+		{
+			desc:         "it returns an LfsAuthenticate command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-lfs-authenticate"),
+			config:       basicConfig,
+			expectedType: &lfsauthenticate.Command{},
+		},
+		{
+			desc:         "it returns a ReceivePack command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-receive-pack"),
+			config:       basicConfig,
+			expectedType: &receivepack.Command{},
+		},
+		{
+			desc:         "it returns an UploadPack command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-upload-pack"),
+			config:       basicConfig,
+			expectedType: &uploadpack.Command{},
+		},
+		{
+			desc:         "it returns an UploadArchive command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-upload-archive"),
+			config:       basicConfig,
+			expectedType: &uploadarchive.Command{},
+		},
+		{
+			desc:         "it returns a PersonalAccessToken command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("personal_access_token"),
+			config:       basicConfig,
+			expectedType: &personalaccesstoken.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := cmd.New(tc.executable, tc.arguments, tc.env, tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
+
+func TestFailingNew(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		expectedError error
+	}{
+		{
+			desc:          "Parsing environment failed",
+			executable:    gitlabShellExec,
+			expectedError: errors.New("Only SSH allowed"),
+		},
+		{
+			desc:          "Unknown command given",
+			executable:    gitlabShellExec,
+			env:           buildEnv("unknown"),
+			expectedError: disallowedcommand.Error,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := cmd.New(tc.executable, []string{}, tc.env, basicConfig, nil)
+			require.Nil(t, command)
+			require.Equal(t, tc.expectedError, err)
+		})
+	}
+}
+
+func buildEnv(command string) sshenv.Env {
+	return sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: command,
+	}
+}
+
+func TestParseSuccess(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		expectedArgs commandargs.CommandArgs
+		expectError  bool
+	}{
+		{
+			desc:         "It sets discover as the command when the command string was empty",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{}, CommandType: commandargs.Discover, Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the key id in any passed arguments",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the key id only if the argument is of <key-id> format",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "username-key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "username-key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the username in any passed arguments",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "username-jane-doe"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "username-jane-doe"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "jane-doe", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It parses 2fa_recovery_codes command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"2fa_recovery_codes"}, CommandType: commandargs.TwoFactorRecover, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"}},
+		},
+		{
+			desc:         "It parses git-receive-pack command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"}},
+		},
+		{
+			desc:         "It parses git-receive-pack command and a project with single quotes",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"}},
+		},
+		{
+			desc:         `It parses "git receive-pack" command`,
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`}},
+		},
+		{
+			desc:         `It parses a command followed by control characters`,
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`}},
+		},
+		{
+			desc:         "It parses git-upload-pack command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-upload-pack", "group/repo"}, CommandType: commandargs.UploadPack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`}},
+		},
+		{
+			desc:         "It parses git-upload-archive command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-upload-archive", "group/repo"}, CommandType: commandargs.UploadArchive, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"}},
+		},
+		{
+			desc:         "It parses git-lfs-authenticate command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-lfs-authenticate", "group/repo", "download"}, CommandType: commandargs.LfsAuthenticate, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"}},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := cmd.Parse(tc.executable, tc.arguments, tc.env)
+
+			if !tc.expectError {
+				require.NoError(t, err)
+				require.Equal(t, tc.expectedArgs, result)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
+
+func TestParseFailure(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		arguments     []string
+		expectedError string
+	}{
+		{
+			desc:          "It fails if SSH connection is not set",
+			executable:    &executable.Executable{Name: executable.GitlabShell},
+			arguments:     []string{},
+			expectedError: "Only SSH allowed",
+		},
+		{
+			desc:          "It fails if SSH command is invalid",
+			executable:    &executable.Executable{Name: executable.GitlabShell},
+			env:           sshenv.Env{IsSSHConnection: true, OriginalCommand: `git receive-pack "`},
+			arguments:     []string{},
+			expectedError: "Invalid SSH command",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err := cmd.Parse(tc.executable, tc.arguments, tc.env)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -4,6 +4,7 @@
 	"fmt"
 	"os"
 
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -34,7 +35,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.GitlabShell, true)
+	executable, err := executable.New(executable.GitlabShell)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
@@ -50,7 +51,7 @@
 	defer logCloser.Close()
 
 	env := sshenv.NewFromEnv()
-	cmd, err := command.New(executable, os.Args[1:], env, config, readWriter)
+	cmd, err := shellCmd.New(executable, os.Args[1:], env, config, readWriter)
 	if err != nil {
 		// For now this could happen if `SSH_CONNECTION` is not set on
 		// the environment
diff --git a/internal/command/command.go b/internal/command/command.go
--- a/internal/command/command.go
+++ b/internal/command/command.go
@@ -3,23 +3,7 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/tracing"
 )
@@ -28,23 +12,6 @@
 	Execute(ctx context.Context) error
 }
 
-func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (Command, error) {
-	var args commandargs.CommandArgs
-	if e.AcceptArgs {
-		var err error
-		args, err = commandargs.Parse(e, arguments, env)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	if cmd := buildCommand(e, args, config, readWriter); cmd != nil {
-		return cmd, nil
-	}
-
-	return nil, disallowedcommand.Error
-}
-
 // Setup() initializes tracing from the configuration file and generates a
 // background context from which all other contexts in the process should derive
 // from, as it has a service name and initial correlation ID set.
@@ -80,53 +47,3 @@
 		closer.Close()
 	}
 }
-
-func buildCommand(e *executable.Executable, args commandargs.CommandArgs, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	switch e.Name {
-	case executable.GitlabShell:
-		return BuildShellCommand(args.(*commandargs.Shell), config, readWriter)
-	case executable.AuthorizedKeysCheck:
-		return buildAuthorizedKeysCommand(args.(*commandargs.AuthorizedKeys), config, readWriter)
-	case executable.AuthorizedPrincipalsCheck:
-		return buildAuthorizedPrincipalsCommand(args.(*commandargs.AuthorizedPrincipals), config, readWriter)
-	case executable.Healthcheck:
-		return buildHealthcheckCommand(config, readWriter)
-	}
-
-	return nil
-}
-
-func BuildShellCommand(args *commandargs.Shell, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	switch args.CommandType {
-	case commandargs.Discover:
-		return &discover.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.TwoFactorRecover:
-		return &twofactorrecover.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.TwoFactorVerify:
-		return &twofactorverify.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.LfsAuthenticate:
-		return &lfsauthenticate.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.ReceivePack:
-		return &receivepack.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.UploadPack:
-		return &uploadpack.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.UploadArchive:
-		return &uploadarchive.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.PersonalAccessToken:
-		return &personalaccesstoken.Command{Config: config, Args: args, ReadWriter: readWriter}
-	}
-
-	return nil
-}
-
-func buildAuthorizedKeysCommand(args *commandargs.AuthorizedKeys, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	return &authorizedkeys.Command{Config: config, Args: args, ReadWriter: readWriter}
-}
-
-func buildAuthorizedPrincipalsCommand(args *commandargs.AuthorizedPrincipals, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	return &authorizedprincipals.Command{Config: config, Args: args, ReadWriter: readWriter}
-}
-
-func buildHealthcheckCommand(config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	return &healthcheck.Command{Config: config, ReadWriter: readWriter}
-}
diff --git a/internal/command/command_test.go b/internal/command/command_test.go
--- a/internal/command/command_test.go
+++ b/internal/command/command_test.go
@@ -1,173 +1,15 @@
 package command
 
 import (
-	"errors"
 	"os"
 	"testing"
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
-var (
-	authorizedKeysExec       = &executable.Executable{Name: executable.AuthorizedKeysCheck, AcceptArgs: true}
-	authorizedPrincipalsExec = &executable.Executable{Name: executable.AuthorizedPrincipalsCheck, AcceptArgs: true}
-	checkExec                = &executable.Executable{Name: executable.Healthcheck, AcceptArgs: false}
-	gitlabShellExec          = &executable.Executable{Name: executable.GitlabShell, AcceptArgs: true}
-
-	basicConfig    = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
-	advancedConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket", SslCertDir: "/tmp/certs"}
-)
-
-func buildEnv(command string) sshenv.Env {
-	return sshenv.Env{
-		IsSSHConnection: true,
-		OriginalCommand: command,
-	}
-}
-
-func TestNew(t *testing.T) {
-	testCases := []struct {
-		desc         string
-		executable   *executable.Executable
-		env          sshenv.Env
-		arguments    []string
-		config       *config.Config
-		expectedType interface{}
-	}{
-		{
-			desc:         "it returns a Discover command",
-			executable:   gitlabShellExec,
-			env:          buildEnv(""),
-			config:       basicConfig,
-			expectedType: &discover.Command{},
-		},
-		{
-			desc:         "it returns a TwoFactorRecover command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("2fa_recovery_codes"),
-			config:       basicConfig,
-			expectedType: &twofactorrecover.Command{},
-		},
-		{
-			desc:         "it returns a TwoFactorVerify command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("2fa_verify"),
-			config:       basicConfig,
-			expectedType: &twofactorverify.Command{},
-		},
-		{
-			desc:         "it returns an LfsAuthenticate command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-lfs-authenticate"),
-			config:       basicConfig,
-			expectedType: &lfsauthenticate.Command{},
-		},
-		{
-			desc:         "it returns a ReceivePack command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-receive-pack"),
-			config:       basicConfig,
-			expectedType: &receivepack.Command{},
-		},
-		{
-			desc:         "it returns an UploadPack command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-upload-pack"),
-			config:       basicConfig,
-			expectedType: &uploadpack.Command{},
-		},
-		{
-			desc:         "it returns an UploadArchive command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-upload-archive"),
-			config:       basicConfig,
-			expectedType: &uploadarchive.Command{},
-		},
-		{
-			desc:         "it returns a Healthcheck command",
-			executable:   checkExec,
-			config:       basicConfig,
-			expectedType: &healthcheck.Command{},
-		},
-		{
-			desc:         "it returns a AuthorizedKeys command",
-			executable:   authorizedKeysExec,
-			arguments:    []string{"git", "git", "key"},
-			config:       basicConfig,
-			expectedType: &authorizedkeys.Command{},
-		},
-		{
-			desc:         "it returns a AuthorizedPrincipals command",
-			executable:   authorizedPrincipalsExec,
-			arguments:    []string{"key", "principal"},
-			config:       basicConfig,
-			expectedType: &authorizedprincipals.Command{},
-		},
-		{
-			desc:         "it returns a PersonalAccessToken command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("personal_access_token"),
-			config:       basicConfig,
-			expectedType: &personalaccesstoken.Command{},
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			command, err := New(tc.executable, tc.arguments, tc.env, tc.config, nil)
-
-			require.NoError(t, err)
-			require.IsType(t, tc.expectedType, command)
-		})
-	}
-}
-
-func TestFailingNew(t *testing.T) {
-	testCases := []struct {
-		desc          string
-		executable    *executable.Executable
-		env           sshenv.Env
-		expectedError error
-	}{
-		{
-			desc:          "Parsing environment failed",
-			executable:    gitlabShellExec,
-			expectedError: errors.New("Only SSH allowed"),
-		},
-		{
-			desc:          "Unknown command given",
-			executable:    gitlabShellExec,
-			env:           buildEnv("unknown"),
-			expectedError: disallowedcommand.Error,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			command, err := New(tc.executable, []string{}, tc.env, basicConfig, nil)
-			require.Nil(t, command)
-			require.Equal(t, tc.expectedError, err)
-		})
-	}
-}
-
 func TestSetup(t *testing.T) {
 	testCases := []struct {
 		name                  string
diff --git a/internal/command/commandargs/command_args.go b/internal/command/commandargs/command_args.go
--- a/internal/command/commandargs/command_args.go
+++ b/internal/command/commandargs/command_args.go
@@ -1,37 +1,8 @@
 package commandargs
 
-import (
-	"errors"
-	"fmt"
-
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-)
-
 type CommandType string
 
 type CommandArgs interface {
 	Parse() error
 	GetArguments() []string
 }
-
-func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (CommandArgs, error) {
-	var args CommandArgs
-
-	switch e.Name {
-	case executable.GitlabShell:
-		args = &Shell{Arguments: arguments, Env: env}
-	case executable.AuthorizedKeysCheck:
-		args = &AuthorizedKeys{Arguments: arguments}
-	case executable.AuthorizedPrincipalsCheck:
-		args = &AuthorizedPrincipals{Arguments: arguments}
-	default:
-		return nil, errors.New(fmt.Sprintf("unknown executable: %s", e.Name))
-	}
-
-	if err := args.Parse(); err != nil {
-		return nil, err
-	}
-
-	return args, nil
-}
diff --git a/internal/command/commandargs/command_args_test.go b/internal/command/commandargs/command_args_test.go
deleted file mode 100644
--- a/internal/command/commandargs/command_args_test.go
+++ /dev/null
@@ -1,197 +0,0 @@
-package commandargs
-
-import (
-	"testing"
-
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestParseSuccess(t *testing.T) {
-	testCases := []struct {
-		desc         string
-		executable   *executable.Executable
-		env          sshenv.Env
-		arguments    []string
-		expectedArgs CommandArgs
-		expectError  bool
-	}{
-		{
-			desc:         "It sets discover as the command when the command string was empty",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{}, CommandType: Discover, Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It finds the key id in any passed arguments",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{"hello", "key-123"},
-			expectedArgs: &Shell{Arguments: []string{"hello", "key-123"}, SshArgs: []string{}, CommandType: Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It finds the key id only if the argument is of <key-id> format",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{"hello", "username-key-123"},
-			expectedArgs: &Shell{Arguments: []string{"hello", "username-key-123"}, SshArgs: []string{}, CommandType: Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It finds the username in any passed arguments",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{"hello", "username-jane-doe"},
-			expectedArgs: &Shell{Arguments: []string{"hello", "username-jane-doe"}, SshArgs: []string{}, CommandType: Discover, GitlabUsername: "jane-doe", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It parses 2fa_recovery_codes command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"2fa_recovery_codes"}, CommandType: TwoFactorRecover, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"}},
-		}, {
-			desc:         "It parses git-receive-pack command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"}},
-		}, {
-			desc:         "It parses git-receive-pack command and a project with single quotes",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"}},
-		}, {
-			desc:         `It parses "git receive-pack" command`,
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`}},
-		}, {
-			desc:         `It parses a command followed by control characters`,
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`}},
-		}, {
-			desc:         "It parses git-upload-pack command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-upload-pack", "group/repo"}, CommandType: UploadPack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`}},
-		}, {
-			desc:         "It parses git-upload-archive command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-upload-archive", "group/repo"}, CommandType: UploadArchive, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"}},
-		}, {
-			desc:         "It parses git-lfs-authenticate command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-lfs-authenticate", "group/repo", "download"}, CommandType: LfsAuthenticate, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"}},
-		}, {
-			desc:         "It parses authorized-keys command",
-			executable:   &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:    []string{"git", "git", "key"},
-			expectedArgs: &AuthorizedKeys{Arguments: []string{"git", "git", "key"}, ExpectedUser: "git", ActualUser: "git", Key: "key"},
-		}, {
-			desc:         "It parses authorized-principals command",
-			executable:   &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:    []string{"key", "principal-1", "principal-2"},
-			expectedArgs: &AuthorizedPrincipals{Arguments: []string{"key", "principal-1", "principal-2"}, KeyId: "key", Principals: []string{"principal-1", "principal-2"}},
-		}, {
-			desc:        "Unknown executable",
-			executable:  &executable.Executable{Name: "unknown"},
-			arguments:   []string{},
-			expectError: true,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			result, err := Parse(tc.executable, tc.arguments, tc.env)
-
-			if !tc.expectError {
-				require.NoError(t, err)
-				require.Equal(t, tc.expectedArgs, result)
-			} else {
-				require.Error(t, err)
-			}
-		})
-	}
-}
-
-func TestParseFailure(t *testing.T) {
-	testCases := []struct {
-		desc          string
-		executable    *executable.Executable
-		env           sshenv.Env
-		arguments     []string
-		expectedError string
-	}{
-		{
-			desc:          "It fails if SSH connection is not set",
-			executable:    &executable.Executable{Name: executable.GitlabShell},
-			arguments:     []string{},
-			expectedError: "Only SSH allowed",
-		},
-		{
-			desc:          "It fails if SSH command is invalid",
-			executable:    &executable.Executable{Name: executable.GitlabShell},
-			env:           sshenv.Env{IsSSHConnection: true, OriginalCommand: `git receive-pack "`},
-			arguments:     []string{},
-			expectedError: "Invalid SSH command",
-		},
-		{
-			desc:          "With not enough arguments for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user"},
-			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
-		},
-		{
-			desc:          "With too many arguments for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user", "user", "key", "something-else"},
-			expectedError: "# Insufficient arguments. 4. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
-		},
-		{
-			desc:          "With missing username for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user", "", "key"},
-			expectedError: "# No username provided",
-		},
-		{
-			desc:          "With missing key for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user", "user", ""},
-			expectedError: "# No key provided",
-		},
-		{
-			desc:          "With not enough arguments for the AuthorizedPrincipalsCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:     []string{"key"},
-			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-principals-check <key-id> <principal1> [<principal2>...]",
-		},
-		{
-			desc:          "With missing key_id for the AuthorizedPrincipalsCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:     []string{"", "principal"},
-			expectedError: "# No key_id provided",
-		},
-		{
-			desc:          "With blank principal for the AuthorizedPrincipalsCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:     []string{"key", "principal", ""},
-			expectedError: "# An invalid principal was provided",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			_, err := Parse(tc.executable, tc.arguments, tc.env)
-
-			require.EqualError(t, err, tc.expectedError)
-		})
-	}
-}
diff --git a/internal/executable/executable.go b/internal/executable/executable.go
--- a/internal/executable/executable.go
+++ b/internal/executable/executable.go
@@ -16,7 +16,6 @@
 type Executable struct {
 	Name       string
 	RootDir    string
-	AcceptArgs bool
 }
 
 var (
@@ -24,7 +23,7 @@
 	osExecutable = os.Executable
 )
 
-func New(name string, acceptArgs bool) (*Executable, error) {
+func New(name string) (*Executable, error) {
 	path, err := osExecutable()
 	if err != nil {
 		return nil, err
@@ -38,7 +37,6 @@
 	executable := &Executable{
 		Name:       name,
 		RootDir:    rootDir,
-		AcceptArgs: acceptArgs,
 	}
 
 	return executable, nil
diff --git a/internal/executable/executable_test.go b/internal/executable/executable_test.go
--- a/internal/executable/executable_test.go
+++ b/internal/executable/executable_test.go
@@ -59,7 +59,7 @@
 			fake.Setup()
 			defer fake.Cleanup()
 
-			result, err := New("gitlab-shell", true)
+			result, err := New("gitlab-shell")
 
 			require.NoError(t, err)
 			require.Equal(t, result.Name, "gitlab-shell")
@@ -96,7 +96,7 @@
 			fake.Setup()
 			defer fake.Cleanup()
 
-			_, err := New("gitlab-shell", true)
+			_, err := New("gitlab-shell")
 
 			require.Error(t, err)
 		})
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -6,7 +6,7 @@
 
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -125,7 +125,7 @@
 		ErrOut: s.channel.Stderr(),
 	}
 
-	cmd := command.BuildShellCommand(args, s.cfg, rw)
+	cmd := shellCmd.Build(args, s.cfg, rw)
 	if cmd == nil {
 		s.toStderr("Unknown command: %v\n", args.CommandType)
 		return 128
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1631113461 0
#      Wed Sep 08 15:04:21 2021 +0000
# Node ID e48762224368d26c2fd014c5bd1bf27923987dba
# Parent  a99f16501b1facae51eb16a109177c695102bc96
refactor: cleanup func signature and remove unused args

diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command.go b/cmd/gitlab-shell-authorized-keys-check/command/command.go
--- a/cmd/gitlab-shell-authorized-keys-check/command/command.go
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command.go
@@ -7,12 +7,10 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
-func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
-	args, err := Parse(e, arguments, env)
+func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(arguments)
 	if err != nil {
 		return nil, err
 	}
@@ -24,7 +22,7 @@
 	return nil, disallowedcommand.Error
 }
 
-func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (*commandargs.AuthorizedKeys, error) {
+func Parse(arguments []string) (*commandargs.AuthorizedKeys, error) {
 	args := &commandargs.AuthorizedKeys{Arguments: arguments}
 
 	if err := args.Parse(); err != nil {
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command_test.go b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
--- a/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
@@ -37,7 +37,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			command, err := command.New(tc.executable, tc.arguments, tc.env, tc.config, nil)
+			command, err := command.New(tc.arguments, tc.config, nil)
 
 			require.NoError(t, err)
 			require.IsType(t, tc.expectedType, command)
@@ -64,7 +64,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			result, err := command.Parse(tc.executable, tc.arguments, tc.env)
+			result, err := command.Parse(tc.arguments)
 
 			if !tc.expectError {
 				require.NoError(t, err)
@@ -112,7 +112,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			_, err := command.Parse(tc.executable, tc.arguments, tc.env)
+			_, err := command.Parse(tc.arguments)
 
 			require.EqualError(t, err, tc.expectedError)
 		})
diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go
--- a/cmd/gitlab-shell-authorized-keys-check/main.go
+++ b/cmd/gitlab-shell-authorized-keys-check/main.go
@@ -11,7 +11,6 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
 func main() {
@@ -36,7 +35,7 @@
 	logCloser := logger.Configure(config)
 	defer logCloser.Close()
 
-	cmd, err := cmd.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
+	cmd, err := cmd.New(os.Args[1:], config, readWriter)
 	if err != nil {
 		// For now this could happen if `SSH_CONNECTION` is not set on
 		// the environment
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command.go b/cmd/gitlab-shell-authorized-principals-check/command/command.go
--- a/cmd/gitlab-shell-authorized-principals-check/command/command.go
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command.go
@@ -7,12 +7,10 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
-func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
-	args, err := Parse(e, arguments, env)
+func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(arguments)
 	if err != nil {
 		return nil, err
 	}
@@ -24,7 +22,7 @@
 	return nil, disallowedcommand.Error
 }
 
-func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (*commandargs.AuthorizedPrincipals, error) {
+func Parse(arguments []string) (*commandargs.AuthorizedPrincipals, error) {
 	args := &commandargs.AuthorizedPrincipals{Arguments: arguments}
 
 	if err := args.Parse(); err != nil {
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command_test.go b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
--- a/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
@@ -37,7 +37,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			command, err := cmd.New(tc.executable, tc.arguments, tc.env, tc.config, nil)
+			command, err := cmd.New(tc.arguments, tc.config, nil)
 
 			require.NoError(t, err)
 			require.IsType(t, tc.expectedType, command)
@@ -64,7 +64,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			result, err := cmd.Parse(tc.executable, tc.arguments, tc.env)
+			result, err := cmd.Parse(tc.arguments)
 
 			if !tc.expectError {
 				require.NoError(t, err)
@@ -106,7 +106,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			_, err := cmd.Parse(tc.executable, tc.arguments, tc.env)
+			_, err := cmd.Parse(tc.arguments)
 
 			require.EqualError(t, err, tc.expectedError)
 		})
diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go
--- a/cmd/gitlab-shell-authorized-principals-check/main.go
+++ b/cmd/gitlab-shell-authorized-principals-check/main.go
@@ -11,7 +11,6 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
 func main() {
@@ -36,7 +35,7 @@
 	logCloser := logger.Configure(config)
 	defer logCloser.Close()
 
-	cmd, err := cmd.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
+	cmd, err := cmd.New(os.Args[1:], config, readWriter)
 	if err != nil {
 		// For now this could happen if `SSH_CONNECTION` is not set on
 		// the environment
diff --git a/cmd/gitlab-shell/command/command.go b/cmd/gitlab-shell/command/command.go
--- a/cmd/gitlab-shell/command/command.go
+++ b/cmd/gitlab-shell/command/command.go
@@ -14,12 +14,11 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
-func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
-	args, err := Parse(e, arguments, env)
+func New(arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(arguments, env)
 	if err != nil {
 		return nil, err
 	}
@@ -31,7 +30,7 @@
 	return nil, disallowedcommand.Error
 }
 
-func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (*commandargs.Shell, error) {
+func Parse(arguments []string, env sshenv.Env) (*commandargs.Shell, error) {
 	args := &commandargs.Shell{Arguments: arguments, Env: env}
 
 	if err := args.Parse(); err != nil {
diff --git a/cmd/gitlab-shell/command/command_test.go b/cmd/gitlab-shell/command/command_test.go
--- a/cmd/gitlab-shell/command/command_test.go
+++ b/cmd/gitlab-shell/command/command_test.go
@@ -95,7 +95,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			command, err := cmd.New(tc.executable, tc.arguments, tc.env, tc.config, nil)
+			command, err := cmd.New(tc.arguments, tc.env, tc.config, nil)
 
 			require.NoError(t, err)
 			require.IsType(t, tc.expectedType, command)
@@ -125,7 +125,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			command, err := cmd.New(tc.executable, []string{}, tc.env, basicConfig, nil)
+			command, err := cmd.New([]string{}, tc.env, basicConfig, nil)
 			require.Nil(t, command)
 			require.Equal(t, tc.expectedError, err)
 		})
@@ -236,7 +236,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			result, err := cmd.Parse(tc.executable, tc.arguments, tc.env)
+			result, err := cmd.Parse(tc.arguments, tc.env)
 
 			if !tc.expectError {
 				require.NoError(t, err)
@@ -273,7 +273,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			_, err := cmd.Parse(tc.executable, tc.arguments, tc.env)
+			_, err := cmd.Parse(tc.arguments, tc.env)
 
 			require.EqualError(t, err, tc.expectedError)
 		})
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -51,7 +51,7 @@
 	defer logCloser.Close()
 
 	env := sshenv.NewFromEnv()
-	cmd, err := shellCmd.New(executable, os.Args[1:], env, config, readWriter)
+	cmd, err := shellCmd.New(os.Args[1:], env, config, readWriter)
 	if err != nil {
 		// For now this could happen if `SSH_CONNECTION` is not set on
 		// the environment
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1631115906 0
#      Wed Sep 08 15:45:06 2021 +0000
# Node ID 59bd705b20bab515eb6048a74038aaeb71be932b
# Parent  e48762224368d26c2fd014c5bd1bf27923987dba
refactor: fix style issues

diff --git a/cmd/check/command/command.go b/cmd/check/command/command.go
--- a/cmd/check/command/command.go
+++ b/cmd/check/command/command.go
@@ -8,7 +8,6 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
-
 func New(config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
 	if cmd := build(config, readWriter); cmd != nil {
 		return cmd, nil
diff --git a/internal/executable/executable.go b/internal/executable/executable.go
--- a/internal/executable/executable.go
+++ b/internal/executable/executable.go
@@ -14,8 +14,8 @@
 )
 
 type Executable struct {
-	Name       string
-	RootDir    string
+	Name    string
+	RootDir string
 }
 
 var (
@@ -35,8 +35,8 @@
 	}
 
 	executable := &Executable{
-		Name:       name,
-		RootDir:    rootDir,
+		Name:    name,
+		RootDir: rootDir,
 	}
 
 	return executable, nil
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1631626953 -3600
#      Tue Sep 14 14:42:33 2021 +0100
# Node ID 7052305b4d4968d49d50e72e54d57a904b2f6f76
# Parent  59bd705b20bab515eb6048a74038aaeb71be932b
# Parent  a6e55095129693b9c697f000caa234685cdc2ba1
Merge branch 'main' into refactor/cmd

diff --git a/.ruby-version b/.ruby-version
--- a/.ruby-version
+++ b/.ruby-version
@@ -1,1 +1,1 @@
-2.7.2
+2.7.4
diff --git a/.tool-versions b/.tool-versions
new file mode 100644
--- /dev/null
+++ b/.tool-versions
@@ -0,0 +1,2 @@
+ruby 2.7.4
+go 1.16.8
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -8,6 +8,8 @@
 
 PREFIX ?= /usr/local
 
+build: bin/gitlab-shell
+
 validate: verify test
 
 verify: verify_golang
@@ -40,7 +42,6 @@
 _script_install:
 	bin/install
 
-build: bin/gitlab-shell
 compile: bin/gitlab-shell
 bin/gitlab-shell: $(GO_SOURCES)
 	GOBIN="$(CURDIR)/bin" go install $(GOBUILD_FLAGS) ./cmd/...
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -32,7 +32,7 @@
 	client, err := config.HttpClient()
 	require.NoError(t, err)
 
-	_, err = client.Get("http://host.com/path")
+	_, err = client.Get(url)
 	require.NoError(t, err)
 
 	ms, err := prometheus.DefaultGatherer.Gather()
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1631627422 0
#      Tue Sep 14 13:50:22 2021 +0000
# Node ID 1e55f7044ac9bb3c90328edc2b168941c9affed3
# Parent  a6e55095129693b9c697f000caa234685cdc2ba1
# Parent  7052305b4d4968d49d50e72e54d57a904b2f6f76
Merge branch 'refactor/cmd' into 'main'

refactor: rearchitect command and executable Go modules

Closes #214

See merge request gitlab-org/gitlab-shell!514

diff --git a/cmd/check/command/command.go b/cmd/check/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/check/command/command.go
@@ -0,0 +1,21 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+)
+
+func New(config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	if cmd := build(config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func build(config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	return &healthcheck.Command{Config: config, ReadWriter: readWriter}
+}
diff --git a/cmd/check/command/command_test.go b/cmd/check/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/check/command/command_test.go
@@ -0,0 +1,42 @@
+package command_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	basicConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a Healthcheck command",
+			config:       basicConfig,
+			expectedType: &healthcheck.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := command.New(tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
diff --git a/cmd/check/main.go b/cmd/check/main.go
--- a/cmd/check/main.go
+++ b/cmd/check/main.go
@@ -4,12 +4,12 @@
 	"fmt"
 	"os"
 
+	checkCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
 func main() {
@@ -19,7 +19,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.Healthcheck, false)
+	executable, err := executable.New(executable.Healthcheck)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
@@ -34,7 +34,7 @@
 	logCloser := logger.Configure(config)
 	defer logCloser.Close()
 
-	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
+	cmd, err := checkCmd.New(config, readWriter)
 	if err != nil {
 		fmt.Fprintf(readWriter.ErrOut, "%v\n", err)
 		os.Exit(1)
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command.go b/cmd/gitlab-shell-authorized-keys-check/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command.go
@@ -0,0 +1,37 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+)
+
+func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(arguments)
+	if err != nil {
+		return nil, err
+	}
+
+	if cmd := build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func Parse(arguments []string) (*commandargs.AuthorizedKeys, error) {
+	args := &commandargs.AuthorizedKeys{Arguments: arguments}
+
+	if err := args.Parse(); err != nil {
+		return nil, err
+	}
+
+	return args, nil
+}
+
+func build(args *commandargs.AuthorizedKeys, config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	return &authorizedkeys.Command{Config: config, Args: args, ReadWriter: readWriter}
+}
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command_test.go b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
@@ -0,0 +1,120 @@
+package command_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	authorizedKeysExec = &executable.Executable{Name: executable.AuthorizedKeysCheck}
+	basicConfig        = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a AuthorizedKeys command",
+			executable:   authorizedKeysExec,
+			arguments:    []string{"git", "git", "key"},
+			config:       basicConfig,
+			expectedType: &authorizedkeys.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := command.New(tc.arguments, tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
+
+func TestParseSuccess(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		expectedArgs commandargs.CommandArgs
+		expectError  bool
+	}{
+		{
+			desc:         "It parses authorized-keys command",
+			executable:   &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:    []string{"git", "git", "key"},
+			expectedArgs: &commandargs.AuthorizedKeys{Arguments: []string{"git", "git", "key"}, ExpectedUser: "git", ActualUser: "git", Key: "key"},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := command.Parse(tc.arguments)
+
+			if !tc.expectError {
+				require.NoError(t, err)
+				require.Equal(t, tc.expectedArgs, result)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
+
+func TestParseFailure(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		arguments     []string
+		expectedError string
+	}{
+		{
+			desc:          "With not enough arguments for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user"},
+			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
+		},
+		{
+			desc:          "With too many arguments for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user", "user", "key", "something-else"},
+			expectedError: "# Insufficient arguments. 4. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
+		},
+		{
+			desc:          "With missing username for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user", "", "key"},
+			expectedError: "# No username provided",
+		},
+		{
+			desc:          "With missing key for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user", "user", ""},
+			expectedError: "# No key provided",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err := command.Parse(tc.arguments)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go
--- a/cmd/gitlab-shell-authorized-keys-check/main.go
+++ b/cmd/gitlab-shell-authorized-keys-check/main.go
@@ -4,13 +4,13 @@
 	"fmt"
 	"os"
 
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
 func main() {
@@ -20,7 +20,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.AuthorizedKeysCheck, true)
+	executable, err := executable.New(executable.AuthorizedKeysCheck)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
@@ -35,7 +35,7 @@
 	logCloser := logger.Configure(config)
 	defer logCloser.Close()
 
-	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
+	cmd, err := cmd.New(os.Args[1:], config, readWriter)
 	if err != nil {
 		// For now this could happen if `SSH_CONNECTION` is not set on
 		// the environment
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command.go b/cmd/gitlab-shell-authorized-principals-check/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command.go
@@ -0,0 +1,37 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+)
+
+func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(arguments)
+	if err != nil {
+		return nil, err
+	}
+
+	if cmd := build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func Parse(arguments []string) (*commandargs.AuthorizedPrincipals, error) {
+	args := &commandargs.AuthorizedPrincipals{Arguments: arguments}
+
+	if err := args.Parse(); err != nil {
+		return nil, err
+	}
+
+	return args, nil
+}
+
+func build(args *commandargs.AuthorizedPrincipals, config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	return &authorizedprincipals.Command{Config: config, Args: args, ReadWriter: readWriter}
+}
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command_test.go b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
@@ -0,0 +1,114 @@
+package command_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	authorizedPrincipalsExec = &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}
+	basicConfig              = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a AuthorizedPrincipals command",
+			executable:   authorizedPrincipalsExec,
+			arguments:    []string{"key", "principal"},
+			config:       basicConfig,
+			expectedType: &authorizedprincipals.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := cmd.New(tc.arguments, tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
+
+func TestParseSuccess(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		expectedArgs commandargs.CommandArgs
+		expectError  bool
+	}{
+		{
+			desc:         "It parses authorized-principals command",
+			executable:   &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:    []string{"key", "principal-1", "principal-2"},
+			expectedArgs: &commandargs.AuthorizedPrincipals{Arguments: []string{"key", "principal-1", "principal-2"}, KeyId: "key", Principals: []string{"principal-1", "principal-2"}},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := cmd.Parse(tc.arguments)
+
+			if !tc.expectError {
+				require.NoError(t, err)
+				require.Equal(t, tc.expectedArgs, result)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
+
+func TestParseFailure(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		arguments     []string
+		expectedError string
+	}{
+		{
+			desc:          "With not enough arguments for the AuthorizedPrincipalsCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:     []string{"key"},
+			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-principals-check <key-id> <principal1> [<principal2>...]",
+		},
+		{
+			desc:          "With missing key_id for the AuthorizedPrincipalsCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:     []string{"", "principal"},
+			expectedError: "# No key_id provided",
+		},
+		{
+			desc:          "With blank principal for the AuthorizedPrincipalsCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:     []string{"key", "principal", ""},
+			expectedError: "# An invalid principal was provided",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err := cmd.Parse(tc.arguments)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go
--- a/cmd/gitlab-shell-authorized-principals-check/main.go
+++ b/cmd/gitlab-shell-authorized-principals-check/main.go
@@ -4,13 +4,13 @@
 	"fmt"
 	"os"
 
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
 func main() {
@@ -20,7 +20,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.AuthorizedPrincipalsCheck, true)
+	executable, err := executable.New(executable.AuthorizedPrincipalsCheck)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
@@ -35,7 +35,7 @@
 	logCloser := logger.Configure(config)
 	defer logCloser.Close()
 
-	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
+	cmd, err := cmd.New(os.Args[1:], config, readWriter)
 	if err != nil {
 		// For now this could happen if `SSH_CONNECTION` is not set on
 		// the environment
diff --git a/cmd/gitlab-shell/command/command.go b/cmd/gitlab-shell/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell/command/command.go
@@ -0,0 +1,64 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+func New(arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(arguments, env)
+	if err != nil {
+		return nil, err
+	}
+
+	if cmd := Build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func Parse(arguments []string, env sshenv.Env) (*commandargs.Shell, error) {
+	args := &commandargs.Shell{Arguments: arguments, Env: env}
+
+	if err := args.Parse(); err != nil {
+		return nil, err
+	}
+
+	return args, nil
+}
+
+func Build(args *commandargs.Shell, config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	switch args.CommandType {
+	case commandargs.Discover:
+		return &discover.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.TwoFactorRecover:
+		return &twofactorrecover.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.TwoFactorVerify:
+		return &twofactorverify.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.LfsAuthenticate:
+		return &lfsauthenticate.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.ReceivePack:
+		return &receivepack.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.UploadPack:
+		return &uploadpack.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.UploadArchive:
+		return &uploadarchive.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.PersonalAccessToken:
+		return &personalaccesstoken.Command{Config: config, Args: args, ReadWriter: readWriter}
+	}
+
+	return nil
+}
diff --git a/cmd/gitlab-shell/command/command_test.go b/cmd/gitlab-shell/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell/command/command_test.go
@@ -0,0 +1,281 @@
+package command_test
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	gitlabShellExec = &executable.Executable{Name: executable.GitlabShell}
+	basicConfig     = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a Discover command",
+			executable:   gitlabShellExec,
+			env:          buildEnv(""),
+			config:       basicConfig,
+			expectedType: &discover.Command{},
+		},
+		{
+			desc:         "it returns a TwoFactorRecover command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("2fa_recovery_codes"),
+			config:       basicConfig,
+			expectedType: &twofactorrecover.Command{},
+		},
+		{
+			desc:         "it returns a TwoFactorVerify command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("2fa_verify"),
+			config:       basicConfig,
+			expectedType: &twofactorverify.Command{},
+		},
+		{
+			desc:         "it returns an LfsAuthenticate command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-lfs-authenticate"),
+			config:       basicConfig,
+			expectedType: &lfsauthenticate.Command{},
+		},
+		{
+			desc:         "it returns a ReceivePack command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-receive-pack"),
+			config:       basicConfig,
+			expectedType: &receivepack.Command{},
+		},
+		{
+			desc:         "it returns an UploadPack command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-upload-pack"),
+			config:       basicConfig,
+			expectedType: &uploadpack.Command{},
+		},
+		{
+			desc:         "it returns an UploadArchive command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-upload-archive"),
+			config:       basicConfig,
+			expectedType: &uploadarchive.Command{},
+		},
+		{
+			desc:         "it returns a PersonalAccessToken command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("personal_access_token"),
+			config:       basicConfig,
+			expectedType: &personalaccesstoken.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := cmd.New(tc.arguments, tc.env, tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
+
+func TestFailingNew(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		expectedError error
+	}{
+		{
+			desc:          "Parsing environment failed",
+			executable:    gitlabShellExec,
+			expectedError: errors.New("Only SSH allowed"),
+		},
+		{
+			desc:          "Unknown command given",
+			executable:    gitlabShellExec,
+			env:           buildEnv("unknown"),
+			expectedError: disallowedcommand.Error,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := cmd.New([]string{}, tc.env, basicConfig, nil)
+			require.Nil(t, command)
+			require.Equal(t, tc.expectedError, err)
+		})
+	}
+}
+
+func buildEnv(command string) sshenv.Env {
+	return sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: command,
+	}
+}
+
+func TestParseSuccess(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		expectedArgs commandargs.CommandArgs
+		expectError  bool
+	}{
+		{
+			desc:         "It sets discover as the command when the command string was empty",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{}, CommandType: commandargs.Discover, Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the key id in any passed arguments",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the key id only if the argument is of <key-id> format",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "username-key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "username-key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the username in any passed arguments",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "username-jane-doe"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "username-jane-doe"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "jane-doe", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It parses 2fa_recovery_codes command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"2fa_recovery_codes"}, CommandType: commandargs.TwoFactorRecover, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"}},
+		},
+		{
+			desc:         "It parses git-receive-pack command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"}},
+		},
+		{
+			desc:         "It parses git-receive-pack command and a project with single quotes",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"}},
+		},
+		{
+			desc:         `It parses "git receive-pack" command`,
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`}},
+		},
+		{
+			desc:         `It parses a command followed by control characters`,
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`}},
+		},
+		{
+			desc:         "It parses git-upload-pack command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-upload-pack", "group/repo"}, CommandType: commandargs.UploadPack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`}},
+		},
+		{
+			desc:         "It parses git-upload-archive command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-upload-archive", "group/repo"}, CommandType: commandargs.UploadArchive, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"}},
+		},
+		{
+			desc:         "It parses git-lfs-authenticate command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-lfs-authenticate", "group/repo", "download"}, CommandType: commandargs.LfsAuthenticate, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"}},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := cmd.Parse(tc.arguments, tc.env)
+
+			if !tc.expectError {
+				require.NoError(t, err)
+				require.Equal(t, tc.expectedArgs, result)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
+
+func TestParseFailure(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		arguments     []string
+		expectedError string
+	}{
+		{
+			desc:          "It fails if SSH connection is not set",
+			executable:    &executable.Executable{Name: executable.GitlabShell},
+			arguments:     []string{},
+			expectedError: "Only SSH allowed",
+		},
+		{
+			desc:          "It fails if SSH command is invalid",
+			executable:    &executable.Executable{Name: executable.GitlabShell},
+			env:           sshenv.Env{IsSSHConnection: true, OriginalCommand: `git receive-pack "`},
+			arguments:     []string{},
+			expectedError: "Invalid SSH command",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err := cmd.Parse(tc.arguments, tc.env)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -4,6 +4,7 @@
 	"fmt"
 	"os"
 
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -34,7 +35,7 @@
 		ErrOut: os.Stderr,
 	}
 
-	executable, err := executable.New(executable.GitlabShell, true)
+	executable, err := executable.New(executable.GitlabShell)
 	if err != nil {
 		fmt.Fprintln(readWriter.ErrOut, "Failed to determine executable, exiting")
 		os.Exit(1)
@@ -50,7 +51,7 @@
 	defer logCloser.Close()
 
 	env := sshenv.NewFromEnv()
-	cmd, err := command.New(executable, os.Args[1:], env, config, readWriter)
+	cmd, err := shellCmd.New(os.Args[1:], env, config, readWriter)
 	if err != nil {
 		// For now this could happen if `SSH_CONNECTION` is not set on
 		// the environment
diff --git a/internal/command/command.go b/internal/command/command.go
--- a/internal/command/command.go
+++ b/internal/command/command.go
@@ -3,23 +3,7 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/tracing"
 )
@@ -28,23 +12,6 @@
 	Execute(ctx context.Context) error
 }
 
-func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (Command, error) {
-	var args commandargs.CommandArgs
-	if e.AcceptArgs {
-		var err error
-		args, err = commandargs.Parse(e, arguments, env)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	if cmd := buildCommand(e, args, config, readWriter); cmd != nil {
-		return cmd, nil
-	}
-
-	return nil, disallowedcommand.Error
-}
-
 // Setup() initializes tracing from the configuration file and generates a
 // background context from which all other contexts in the process should derive
 // from, as it has a service name and initial correlation ID set.
@@ -80,53 +47,3 @@
 		closer.Close()
 	}
 }
-
-func buildCommand(e *executable.Executable, args commandargs.CommandArgs, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	switch e.Name {
-	case executable.GitlabShell:
-		return BuildShellCommand(args.(*commandargs.Shell), config, readWriter)
-	case executable.AuthorizedKeysCheck:
-		return buildAuthorizedKeysCommand(args.(*commandargs.AuthorizedKeys), config, readWriter)
-	case executable.AuthorizedPrincipalsCheck:
-		return buildAuthorizedPrincipalsCommand(args.(*commandargs.AuthorizedPrincipals), config, readWriter)
-	case executable.Healthcheck:
-		return buildHealthcheckCommand(config, readWriter)
-	}
-
-	return nil
-}
-
-func BuildShellCommand(args *commandargs.Shell, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	switch args.CommandType {
-	case commandargs.Discover:
-		return &discover.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.TwoFactorRecover:
-		return &twofactorrecover.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.TwoFactorVerify:
-		return &twofactorverify.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.LfsAuthenticate:
-		return &lfsauthenticate.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.ReceivePack:
-		return &receivepack.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.UploadPack:
-		return &uploadpack.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.UploadArchive:
-		return &uploadarchive.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.PersonalAccessToken:
-		return &personalaccesstoken.Command{Config: config, Args: args, ReadWriter: readWriter}
-	}
-
-	return nil
-}
-
-func buildAuthorizedKeysCommand(args *commandargs.AuthorizedKeys, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	return &authorizedkeys.Command{Config: config, Args: args, ReadWriter: readWriter}
-}
-
-func buildAuthorizedPrincipalsCommand(args *commandargs.AuthorizedPrincipals, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	return &authorizedprincipals.Command{Config: config, Args: args, ReadWriter: readWriter}
-}
-
-func buildHealthcheckCommand(config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	return &healthcheck.Command{Config: config, ReadWriter: readWriter}
-}
diff --git a/internal/command/command_test.go b/internal/command/command_test.go
--- a/internal/command/command_test.go
+++ b/internal/command/command_test.go
@@ -1,173 +1,15 @@
 package command
 
 import (
-	"errors"
 	"os"
 	"testing"
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
-var (
-	authorizedKeysExec       = &executable.Executable{Name: executable.AuthorizedKeysCheck, AcceptArgs: true}
-	authorizedPrincipalsExec = &executable.Executable{Name: executable.AuthorizedPrincipalsCheck, AcceptArgs: true}
-	checkExec                = &executable.Executable{Name: executable.Healthcheck, AcceptArgs: false}
-	gitlabShellExec          = &executable.Executable{Name: executable.GitlabShell, AcceptArgs: true}
-
-	basicConfig    = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
-	advancedConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket", SslCertDir: "/tmp/certs"}
-)
-
-func buildEnv(command string) sshenv.Env {
-	return sshenv.Env{
-		IsSSHConnection: true,
-		OriginalCommand: command,
-	}
-}
-
-func TestNew(t *testing.T) {
-	testCases := []struct {
-		desc         string
-		executable   *executable.Executable
-		env          sshenv.Env
-		arguments    []string
-		config       *config.Config
-		expectedType interface{}
-	}{
-		{
-			desc:         "it returns a Discover command",
-			executable:   gitlabShellExec,
-			env:          buildEnv(""),
-			config:       basicConfig,
-			expectedType: &discover.Command{},
-		},
-		{
-			desc:         "it returns a TwoFactorRecover command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("2fa_recovery_codes"),
-			config:       basicConfig,
-			expectedType: &twofactorrecover.Command{},
-		},
-		{
-			desc:         "it returns a TwoFactorVerify command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("2fa_verify"),
-			config:       basicConfig,
-			expectedType: &twofactorverify.Command{},
-		},
-		{
-			desc:         "it returns an LfsAuthenticate command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-lfs-authenticate"),
-			config:       basicConfig,
-			expectedType: &lfsauthenticate.Command{},
-		},
-		{
-			desc:         "it returns a ReceivePack command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-receive-pack"),
-			config:       basicConfig,
-			expectedType: &receivepack.Command{},
-		},
-		{
-			desc:         "it returns an UploadPack command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-upload-pack"),
-			config:       basicConfig,
-			expectedType: &uploadpack.Command{},
-		},
-		{
-			desc:         "it returns an UploadArchive command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-upload-archive"),
-			config:       basicConfig,
-			expectedType: &uploadarchive.Command{},
-		},
-		{
-			desc:         "it returns a Healthcheck command",
-			executable:   checkExec,
-			config:       basicConfig,
-			expectedType: &healthcheck.Command{},
-		},
-		{
-			desc:         "it returns a AuthorizedKeys command",
-			executable:   authorizedKeysExec,
-			arguments:    []string{"git", "git", "key"},
-			config:       basicConfig,
-			expectedType: &authorizedkeys.Command{},
-		},
-		{
-			desc:         "it returns a AuthorizedPrincipals command",
-			executable:   authorizedPrincipalsExec,
-			arguments:    []string{"key", "principal"},
-			config:       basicConfig,
-			expectedType: &authorizedprincipals.Command{},
-		},
-		{
-			desc:         "it returns a PersonalAccessToken command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("personal_access_token"),
-			config:       basicConfig,
-			expectedType: &personalaccesstoken.Command{},
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			command, err := New(tc.executable, tc.arguments, tc.env, tc.config, nil)
-
-			require.NoError(t, err)
-			require.IsType(t, tc.expectedType, command)
-		})
-	}
-}
-
-func TestFailingNew(t *testing.T) {
-	testCases := []struct {
-		desc          string
-		executable    *executable.Executable
-		env           sshenv.Env
-		expectedError error
-	}{
-		{
-			desc:          "Parsing environment failed",
-			executable:    gitlabShellExec,
-			expectedError: errors.New("Only SSH allowed"),
-		},
-		{
-			desc:          "Unknown command given",
-			executable:    gitlabShellExec,
-			env:           buildEnv("unknown"),
-			expectedError: disallowedcommand.Error,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			command, err := New(tc.executable, []string{}, tc.env, basicConfig, nil)
-			require.Nil(t, command)
-			require.Equal(t, tc.expectedError, err)
-		})
-	}
-}
-
 func TestSetup(t *testing.T) {
 	testCases := []struct {
 		name                  string
diff --git a/internal/command/commandargs/command_args.go b/internal/command/commandargs/command_args.go
--- a/internal/command/commandargs/command_args.go
+++ b/internal/command/commandargs/command_args.go
@@ -1,37 +1,8 @@
 package commandargs
 
-import (
-	"errors"
-	"fmt"
-
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-)
-
 type CommandType string
 
 type CommandArgs interface {
 	Parse() error
 	GetArguments() []string
 }
-
-func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (CommandArgs, error) {
-	var args CommandArgs
-
-	switch e.Name {
-	case executable.GitlabShell:
-		args = &Shell{Arguments: arguments, Env: env}
-	case executable.AuthorizedKeysCheck:
-		args = &AuthorizedKeys{Arguments: arguments}
-	case executable.AuthorizedPrincipalsCheck:
-		args = &AuthorizedPrincipals{Arguments: arguments}
-	default:
-		return nil, errors.New(fmt.Sprintf("unknown executable: %s", e.Name))
-	}
-
-	if err := args.Parse(); err != nil {
-		return nil, err
-	}
-
-	return args, nil
-}
diff --git a/internal/command/commandargs/command_args_test.go b/internal/command/commandargs/command_args_test.go
deleted file mode 100644
--- a/internal/command/commandargs/command_args_test.go
+++ /dev/null
@@ -1,197 +0,0 @@
-package commandargs
-
-import (
-	"testing"
-
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestParseSuccess(t *testing.T) {
-	testCases := []struct {
-		desc         string
-		executable   *executable.Executable
-		env          sshenv.Env
-		arguments    []string
-		expectedArgs CommandArgs
-		expectError  bool
-	}{
-		{
-			desc:         "It sets discover as the command when the command string was empty",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{}, CommandType: Discover, Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It finds the key id in any passed arguments",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{"hello", "key-123"},
-			expectedArgs: &Shell{Arguments: []string{"hello", "key-123"}, SshArgs: []string{}, CommandType: Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It finds the key id only if the argument is of <key-id> format",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{"hello", "username-key-123"},
-			expectedArgs: &Shell{Arguments: []string{"hello", "username-key-123"}, SshArgs: []string{}, CommandType: Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It finds the username in any passed arguments",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{"hello", "username-jane-doe"},
-			expectedArgs: &Shell{Arguments: []string{"hello", "username-jane-doe"}, SshArgs: []string{}, CommandType: Discover, GitlabUsername: "jane-doe", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It parses 2fa_recovery_codes command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"2fa_recovery_codes"}, CommandType: TwoFactorRecover, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"}},
-		}, {
-			desc:         "It parses git-receive-pack command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"}},
-		}, {
-			desc:         "It parses git-receive-pack command and a project with single quotes",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"}},
-		}, {
-			desc:         `It parses "git receive-pack" command`,
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`}},
-		}, {
-			desc:         `It parses a command followed by control characters`,
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`}},
-		}, {
-			desc:         "It parses git-upload-pack command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-upload-pack", "group/repo"}, CommandType: UploadPack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`}},
-		}, {
-			desc:         "It parses git-upload-archive command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-upload-archive", "group/repo"}, CommandType: UploadArchive, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"}},
-		}, {
-			desc:         "It parses git-lfs-authenticate command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-lfs-authenticate", "group/repo", "download"}, CommandType: LfsAuthenticate, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"}},
-		}, {
-			desc:         "It parses authorized-keys command",
-			executable:   &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:    []string{"git", "git", "key"},
-			expectedArgs: &AuthorizedKeys{Arguments: []string{"git", "git", "key"}, ExpectedUser: "git", ActualUser: "git", Key: "key"},
-		}, {
-			desc:         "It parses authorized-principals command",
-			executable:   &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:    []string{"key", "principal-1", "principal-2"},
-			expectedArgs: &AuthorizedPrincipals{Arguments: []string{"key", "principal-1", "principal-2"}, KeyId: "key", Principals: []string{"principal-1", "principal-2"}},
-		}, {
-			desc:        "Unknown executable",
-			executable:  &executable.Executable{Name: "unknown"},
-			arguments:   []string{},
-			expectError: true,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			result, err := Parse(tc.executable, tc.arguments, tc.env)
-
-			if !tc.expectError {
-				require.NoError(t, err)
-				require.Equal(t, tc.expectedArgs, result)
-			} else {
-				require.Error(t, err)
-			}
-		})
-	}
-}
-
-func TestParseFailure(t *testing.T) {
-	testCases := []struct {
-		desc          string
-		executable    *executable.Executable
-		env           sshenv.Env
-		arguments     []string
-		expectedError string
-	}{
-		{
-			desc:          "It fails if SSH connection is not set",
-			executable:    &executable.Executable{Name: executable.GitlabShell},
-			arguments:     []string{},
-			expectedError: "Only SSH allowed",
-		},
-		{
-			desc:          "It fails if SSH command is invalid",
-			executable:    &executable.Executable{Name: executable.GitlabShell},
-			env:           sshenv.Env{IsSSHConnection: true, OriginalCommand: `git receive-pack "`},
-			arguments:     []string{},
-			expectedError: "Invalid SSH command",
-		},
-		{
-			desc:          "With not enough arguments for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user"},
-			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
-		},
-		{
-			desc:          "With too many arguments for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user", "user", "key", "something-else"},
-			expectedError: "# Insufficient arguments. 4. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
-		},
-		{
-			desc:          "With missing username for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user", "", "key"},
-			expectedError: "# No username provided",
-		},
-		{
-			desc:          "With missing key for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user", "user", ""},
-			expectedError: "# No key provided",
-		},
-		{
-			desc:          "With not enough arguments for the AuthorizedPrincipalsCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:     []string{"key"},
-			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-principals-check <key-id> <principal1> [<principal2>...]",
-		},
-		{
-			desc:          "With missing key_id for the AuthorizedPrincipalsCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:     []string{"", "principal"},
-			expectedError: "# No key_id provided",
-		},
-		{
-			desc:          "With blank principal for the AuthorizedPrincipalsCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:     []string{"key", "principal", ""},
-			expectedError: "# An invalid principal was provided",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			_, err := Parse(tc.executable, tc.arguments, tc.env)
-
-			require.EqualError(t, err, tc.expectedError)
-		})
-	}
-}
diff --git a/internal/executable/executable.go b/internal/executable/executable.go
--- a/internal/executable/executable.go
+++ b/internal/executable/executable.go
@@ -14,9 +14,8 @@
 )
 
 type Executable struct {
-	Name       string
-	RootDir    string
-	AcceptArgs bool
+	Name    string
+	RootDir string
 }
 
 var (
@@ -24,7 +23,7 @@
 	osExecutable = os.Executable
 )
 
-func New(name string, acceptArgs bool) (*Executable, error) {
+func New(name string) (*Executable, error) {
 	path, err := osExecutable()
 	if err != nil {
 		return nil, err
@@ -36,9 +35,8 @@
 	}
 
 	executable := &Executable{
-		Name:       name,
-		RootDir:    rootDir,
-		AcceptArgs: acceptArgs,
+		Name:    name,
+		RootDir: rootDir,
 	}
 
 	return executable, nil
diff --git a/internal/executable/executable_test.go b/internal/executable/executable_test.go
--- a/internal/executable/executable_test.go
+++ b/internal/executable/executable_test.go
@@ -59,7 +59,7 @@
 			fake.Setup()
 			defer fake.Cleanup()
 
-			result, err := New("gitlab-shell", true)
+			result, err := New("gitlab-shell")
 
 			require.NoError(t, err)
 			require.Equal(t, result.Name, "gitlab-shell")
@@ -96,7 +96,7 @@
 			fake.Setup()
 			defer fake.Cleanup()
 
-			_, err := New("gitlab-shell", true)
+			_, err := New("gitlab-shell")
 
 			require.Error(t, err)
 		})
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -6,7 +6,7 @@
 
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -125,7 +125,7 @@
 		ErrOut: s.channel.Stderr(),
 	}
 
-	cmd := command.BuildShellCommand(args, s.cfg, rw)
+	cmd := shellCmd.Build(args, s.cfg, rw)
 	if cmd == nil {
 		s.toStderr("Unknown command: %v\n", args.CommandType)
 		return 128
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1631619467 -10800
#      Tue Sep 14 14:37:47 2021 +0300
# Node ID ad0c0d6fff0e957bd43ba0bd1813d5325585b689
# Parent  1e55f7044ac9bb3c90328edc2b168941c9affed3
Add TestInvalidClientConfig and TestNewServerWithoutHosts for sshd.go

diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -104,6 +104,22 @@
 	require.Equal(t, 200, r.Result().StatusCode)
 }
 
+func TestNewServerWithoutHosts(t *testing.T) {
+	_, err := NewServer(&config.Config{GitlabUrl: "http://localhost"})
+
+	require.Error(t, err)
+	require.Equal(t, "No host keys could be loaded, aborting", err.Error())
+}
+
+func TestInvalidClientConfig(t *testing.T) {
+	setupServer(t)
+
+	cfg := clientConfig(t)
+	cfg.User = "unknown"
+	_, err := ssh.Dial("tcp", serverUrl, cfg)
+	require.Error(t, err)
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1631697147 0
#      Wed Sep 15 09:12:27 2021 +0000
# Node ID f529d059d39fc51b54a7f166913f43372a3a72f4
# Parent  1e55f7044ac9bb3c90328edc2b168941c9affed3
# Parent  ad0c0d6fff0e957bd43ba0bd1813d5325585b689
Merge branch 'id-sshd-tests' into 'main'

Add TestInvalidClientConfig and TestNewServerWithoutHosts for sshd.go

See merge request gitlab-org/gitlab-shell!518

diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -104,6 +104,22 @@
 	require.Equal(t, 200, r.Result().StatusCode)
 }
 
+func TestNewServerWithoutHosts(t *testing.T) {
+	_, err := NewServer(&config.Config{GitlabUrl: "http://localhost"})
+
+	require.Error(t, err)
+	require.Equal(t, "No host keys could be loaded, aborting", err.Error())
+}
+
+func TestInvalidClientConfig(t *testing.T) {
+	setupServer(t)
+
+	cfg := clientConfig(t)
+	cfg.User = "unknown"
+	_, err := ssh.Dial("tcp", serverUrl, cfg)
+	require.Error(t, err)
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1631701688 -10800
#      Wed Sep 15 13:28:08 2021 +0300
# Node ID ad2f264f1ce8a85761cbaed5f6a490d53d292bf1
# Parent  f529d059d39fc51b54a7f166913f43372a3a72f4
Unit test sshd.handleEnv function

diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/session_test.go
@@ -0,0 +1,44 @@
+package sshd
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"golang.org/x/crypto/ssh"
+)
+
+func TestHandleEnv(t *testing.T) {
+	testCases := []struct {
+		desc                    string
+		payload                 []byte
+		expectedProtocolVersion string
+		expectedResult          bool
+	}{
+		{
+			desc:                    "invalid payload",
+			payload:                 []byte("invalid"),
+			expectedProtocolVersion: "1",
+			expectedResult:          false,
+		}, {
+			desc:                    "valid payload",
+			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL", Value: "2"}),
+			expectedProtocolVersion: "2",
+			expectedResult:          true,
+		}, {
+			desc:                    "valid payload with forbidden env var",
+			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL_ENV", Value: "2"}),
+			expectedProtocolVersion: "1",
+			expectedResult:          true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			s := &session{gitProtocolVersion: "1"}
+			r := &ssh.Request{Payload: tc.payload}
+
+			require.Equal(t, s.handleEnv(r), tc.expectedResult)
+			require.Equal(t, s.gitProtocolVersion, tc.expectedProtocolVersion)
+		})
+	}
+}
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1631704869 0
#      Wed Sep 15 11:21:09 2021 +0000
# Node ID 1758ccc5961c9f68fc3d9335460059b876539924
# Parent  f529d059d39fc51b54a7f166913f43372a3a72f4
# Parent  ad2f264f1ce8a85761cbaed5f6a490d53d292bf1
Merge branch 'id-session-test' into 'main'

Unit test sshd.handleEnv function

See merge request gitlab-org/gitlab-shell!521

diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/session_test.go
@@ -0,0 +1,44 @@
+package sshd
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"golang.org/x/crypto/ssh"
+)
+
+func TestHandleEnv(t *testing.T) {
+	testCases := []struct {
+		desc                    string
+		payload                 []byte
+		expectedProtocolVersion string
+		expectedResult          bool
+	}{
+		{
+			desc:                    "invalid payload",
+			payload:                 []byte("invalid"),
+			expectedProtocolVersion: "1",
+			expectedResult:          false,
+		}, {
+			desc:                    "valid payload",
+			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL", Value: "2"}),
+			expectedProtocolVersion: "2",
+			expectedResult:          true,
+		}, {
+			desc:                    "valid payload with forbidden env var",
+			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL_ENV", Value: "2"}),
+			expectedProtocolVersion: "1",
+			expectedResult:          true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			s := &session{gitProtocolVersion: "1"}
+			r := &ssh.Request{Payload: tc.payload}
+
+			require.Equal(t, s.handleEnv(r), tc.expectedResult)
+			require.Equal(t, s.gitProtocolVersion, tc.expectedProtocolVersion)
+		})
+	}
+}
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1631713284 -10800
#      Wed Sep 15 16:41:24 2021 +0300
# Node ID d0d81a5dcf44095a9eea8fff7fb4eed271ca113c
# Parent  1758ccc5961c9f68fc3d9335460059b876539924
Unit test exit-codes for sshd/session.go

diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -1,12 +1,61 @@
 package sshd
 
 import (
+	"bytes"
+	"context"
+	"io"
+	"net/http"
 	"testing"
 
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
+type fakeChannel struct {
+	stdErr             io.ReadWriter
+	sentRequestName    string
+	sentRequestPayload []byte
+}
+
+func (f *fakeChannel) Read(data []byte) (int, error) {
+	return 0, nil
+}
+
+func (f *fakeChannel) Write(data []byte) (int, error) {
+	return 0, nil
+}
+
+func (f *fakeChannel) Close() error {
+	return nil
+}
+
+func (f *fakeChannel) CloseWrite() error {
+	return nil
+}
+
+func (f *fakeChannel) SendRequest(name string, wantReply bool, payload []byte) (bool, error) {
+	f.sentRequestName = name
+	f.sentRequestPayload = payload
+
+	return true, nil
+}
+
+func (f *fakeChannel) Stderr() io.ReadWriter {
+	return f.stdErr
+}
+
+var requests = []testserver.TestRequestHandler{
+	{
+		Path: "/api/v4/internal/discover",
+		Handler: func(w http.ResponseWriter, r *http.Request) {
+			w.Write([]byte(`{"id": 1000, "name": "Test User", "username": "test-user"}`))
+		},
+	},
+}
+
 func TestHandleEnv(t *testing.T) {
 	testCases := []struct {
 		desc                    string
@@ -42,3 +91,99 @@
 		})
 	}
 }
+
+func TestHandleExec(t *testing.T) {
+	testCases := []struct {
+		desc               string
+		payload            []byte
+		expectedExecCmd    string
+		sentRequestName    string
+		sentRequestPayload []byte
+	}{
+		{
+			desc:            "invalid payload",
+			payload:         []byte("invalid"),
+			expectedExecCmd: "",
+			sentRequestName: "",
+		}, {
+			desc:               "valid payload",
+			payload:            ssh.Marshal(execRequest{Command: "discover"}),
+			expectedExecCmd:    "discover",
+			sentRequestName:    "exit-status",
+			sentRequestPayload: ssh.Marshal(exitStatusReq{ExitStatus: 0}),
+		},
+	}
+
+	url := testserver.StartHttpServer(t, requests)
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			out := &bytes.Buffer{}
+			f := &fakeChannel{stdErr: out}
+			s := &session{
+				gitlabKeyId: "root",
+				channel:     f,
+				cfg:         &config.Config{GitlabUrl: url},
+			}
+			r := &ssh.Request{Payload: tc.payload}
+
+			require.Equal(t, false, s.handleExec(context.Background(), r))
+			require.Equal(t, tc.sentRequestName, f.sentRequestName)
+			require.Equal(t, tc.sentRequestPayload, f.sentRequestPayload)
+		})
+	}
+}
+
+func TestHandleShell(t *testing.T) {
+	testCases := []struct {
+		desc             string
+		cmd              string
+		errMsg           string
+		gitlabKeyId      string
+		expectedExitCode uint32
+	}{
+		{
+			desc:             "fails to parse command",
+			cmd:              `\`,
+			errMsg:           "Failed to parse command: invalid command line string\n",
+			gitlabKeyId:      "root",
+			expectedExitCode: 128,
+		}, {
+			desc:             "specified command is unknown",
+			cmd:              "unknown-command",
+			errMsg:           "Unknown command: unknown-command\n",
+			gitlabKeyId:      "root",
+			expectedExitCode: 128,
+		}, {
+			desc:             "fails to parse command",
+			cmd:              "discover",
+			gitlabKeyId:      "",
+			errMsg:           "remote: ERROR: Failed to get username: who='' is invalid\n",
+			expectedExitCode: 1,
+		}, {
+			desc:             "fails to parse command",
+			cmd:              "discover",
+			errMsg:           "",
+			gitlabKeyId:      "root",
+			expectedExitCode: 0,
+		},
+	}
+
+	url := testserver.StartHttpServer(t, requests)
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			out := &bytes.Buffer{}
+			s := &session{
+				gitlabKeyId: tc.gitlabKeyId,
+				execCmd:     tc.cmd,
+				channel:     &fakeChannel{stdErr: out},
+				cfg:         &config.Config{GitlabUrl: url},
+			}
+			r := &ssh.Request{}
+
+			require.Equal(t, tc.expectedExitCode, s.handleShell(context.Background(), r))
+			require.Equal(t, tc.errMsg, out.String())
+		})
+	}
+}
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1631716784 0
#      Wed Sep 15 14:39:44 2021 +0000
# Node ID ae1afbe2d7390f14489d9c61baadc06fae4f8d51
# Parent  1758ccc5961c9f68fc3d9335460059b876539924
# Parent  d0d81a5dcf44095a9eea8fff7fb4eed271ca113c
Merge branch 'id-session-test-2' into 'main'

Unit test exit-codes for sshd/session.go

Closes #522

See merge request gitlab-org/gitlab-shell!522

diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -1,12 +1,61 @@
 package sshd
 
 import (
+	"bytes"
+	"context"
+	"io"
+	"net/http"
 	"testing"
 
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
+type fakeChannel struct {
+	stdErr             io.ReadWriter
+	sentRequestName    string
+	sentRequestPayload []byte
+}
+
+func (f *fakeChannel) Read(data []byte) (int, error) {
+	return 0, nil
+}
+
+func (f *fakeChannel) Write(data []byte) (int, error) {
+	return 0, nil
+}
+
+func (f *fakeChannel) Close() error {
+	return nil
+}
+
+func (f *fakeChannel) CloseWrite() error {
+	return nil
+}
+
+func (f *fakeChannel) SendRequest(name string, wantReply bool, payload []byte) (bool, error) {
+	f.sentRequestName = name
+	f.sentRequestPayload = payload
+
+	return true, nil
+}
+
+func (f *fakeChannel) Stderr() io.ReadWriter {
+	return f.stdErr
+}
+
+var requests = []testserver.TestRequestHandler{
+	{
+		Path: "/api/v4/internal/discover",
+		Handler: func(w http.ResponseWriter, r *http.Request) {
+			w.Write([]byte(`{"id": 1000, "name": "Test User", "username": "test-user"}`))
+		},
+	},
+}
+
 func TestHandleEnv(t *testing.T) {
 	testCases := []struct {
 		desc                    string
@@ -42,3 +91,99 @@
 		})
 	}
 }
+
+func TestHandleExec(t *testing.T) {
+	testCases := []struct {
+		desc               string
+		payload            []byte
+		expectedExecCmd    string
+		sentRequestName    string
+		sentRequestPayload []byte
+	}{
+		{
+			desc:            "invalid payload",
+			payload:         []byte("invalid"),
+			expectedExecCmd: "",
+			sentRequestName: "",
+		}, {
+			desc:               "valid payload",
+			payload:            ssh.Marshal(execRequest{Command: "discover"}),
+			expectedExecCmd:    "discover",
+			sentRequestName:    "exit-status",
+			sentRequestPayload: ssh.Marshal(exitStatusReq{ExitStatus: 0}),
+		},
+	}
+
+	url := testserver.StartHttpServer(t, requests)
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			out := &bytes.Buffer{}
+			f := &fakeChannel{stdErr: out}
+			s := &session{
+				gitlabKeyId: "root",
+				channel:     f,
+				cfg:         &config.Config{GitlabUrl: url},
+			}
+			r := &ssh.Request{Payload: tc.payload}
+
+			require.Equal(t, false, s.handleExec(context.Background(), r))
+			require.Equal(t, tc.sentRequestName, f.sentRequestName)
+			require.Equal(t, tc.sentRequestPayload, f.sentRequestPayload)
+		})
+	}
+}
+
+func TestHandleShell(t *testing.T) {
+	testCases := []struct {
+		desc             string
+		cmd              string
+		errMsg           string
+		gitlabKeyId      string
+		expectedExitCode uint32
+	}{
+		{
+			desc:             "fails to parse command",
+			cmd:              `\`,
+			errMsg:           "Failed to parse command: invalid command line string\n",
+			gitlabKeyId:      "root",
+			expectedExitCode: 128,
+		}, {
+			desc:             "specified command is unknown",
+			cmd:              "unknown-command",
+			errMsg:           "Unknown command: unknown-command\n",
+			gitlabKeyId:      "root",
+			expectedExitCode: 128,
+		}, {
+			desc:             "fails to parse command",
+			cmd:              "discover",
+			gitlabKeyId:      "",
+			errMsg:           "remote: ERROR: Failed to get username: who='' is invalid\n",
+			expectedExitCode: 1,
+		}, {
+			desc:             "fails to parse command",
+			cmd:              "discover",
+			errMsg:           "",
+			gitlabKeyId:      "root",
+			expectedExitCode: 0,
+		},
+	}
+
+	url := testserver.StartHttpServer(t, requests)
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			out := &bytes.Buffer{}
+			s := &session{
+				gitlabKeyId: tc.gitlabKeyId,
+				execCmd:     tc.cmd,
+				channel:     &fakeChannel{stdErr: out},
+				cfg:         &config.Config{GitlabUrl: url},
+			}
+			r := &ssh.Request{}
+
+			require.Equal(t, tc.expectedExitCode, s.handleShell(context.Background(), r))
+			require.Equal(t, tc.errMsg, out.String())
+		})
+	}
+}
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1631721507 -10800
#      Wed Sep 15 18:58:27 2021 +0300
# Node ID f48622b668b43d66466c50035198c262e66ab6eb
# Parent  ae1afbe2d7390f14489d9c61baadc06fae4f8d51
Extract server config related code out of sshd.go

diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/server_config.go
@@ -0,0 +1,94 @@
+package sshd
+
+import (
+	"context"
+	"encoding/base64"
+	"fmt"
+	"os"
+	"strconv"
+	"time"
+
+	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
+
+	"gitlab.com/gitlab-org/labkit/log"
+)
+
+type serverConfig struct {
+	cfg                  *config.Config
+	hostKeys             []ssh.Signer
+	authorizedKeysClient *authorizedkeys.Client
+}
+
+func newServerConfig(cfg *config.Config) (*serverConfig, error) {
+	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+	}
+
+	var hostKeys []ssh.Signer
+	for _, filename := range cfg.Server.HostKeyFiles {
+		keyRaw, err := os.ReadFile(filename)
+		if err != nil {
+			log.WithError(err).Warnf("Failed to read host key %v", filename)
+			continue
+		}
+		key, err := ssh.ParsePrivateKey(keyRaw)
+		if err != nil {
+			log.WithError(err).Warnf("Failed to parse host key %v", filename)
+			continue
+		}
+
+		hostKeys = append(hostKeys, key)
+	}
+	if len(hostKeys) == 0 {
+		return nil, fmt.Errorf("No host keys could be loaded, aborting")
+	}
+
+	return &serverConfig{cfg: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
+}
+
+func (s *serverConfig) getAuthKey(ctx context.Context, user string, key ssh.PublicKey) (*authorizedkeys.Response, error) {
+	if user != s.cfg.User {
+		return nil, fmt.Errorf("unknown user")
+	}
+	if key.Type() == ssh.KeyAlgoDSA {
+		return nil, fmt.Errorf("DSA is prohibited")
+	}
+
+	ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
+	defer cancel()
+
+	res, err := s.authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
+	if err != nil {
+		return nil, err
+	}
+
+	return res, nil
+}
+
+func (s *serverConfig) get(ctx context.Context) *ssh.ServerConfig {
+	sshCfg := &ssh.ServerConfig{
+		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
+			res, err := s.getAuthKey(ctx, conn.User(), key)
+			if err != nil {
+				return nil, err
+			}
+
+			return &ssh.Permissions{
+				// Record the public key used for authentication.
+				Extensions: map[string]string{
+					"key-id": strconv.FormatInt(res.Id, 10),
+				},
+			}, nil
+		},
+	}
+
+	for _, key := range s.hostKeys {
+		sshCfg.AddHostKey(key)
+	}
+
+	return sshCfg
+}
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/server_config_test.go
@@ -0,0 +1,105 @@
+package sshd
+
+import (
+	"context"
+	"crypto/dsa"
+	"crypto/rand"
+	"crypto/rsa"
+	"path"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+)
+
+func TestNewServerConfigWithoutHosts(t *testing.T) {
+	_, err := newServerConfig(&config.Config{GitlabUrl: "http://localhost"})
+
+	require.Error(t, err)
+	require.Equal(t, "No host keys could be loaded, aborting", err.Error())
+}
+
+func TestFailedAuthorizedKeysClient(t *testing.T) {
+	_, err := newServerConfig(&config.Config{GitlabUrl: "ftp://localhost"})
+
+	require.Error(t, err)
+	require.Equal(t, "failed to initialize GitLab client: Error creating http client: unknown GitLab URL prefix", err.Error())
+}
+
+func TestFailedGetAuthKey(t *testing.T) {
+	testhelper.PrepareTestRootDir(t)
+
+	srvCfg := config.ServerConfig{
+		Listen:                  "127.0.0.1",
+		ConcurrentSessionsLimit: 1,
+		HostKeyFiles: []string{
+			path.Join(testhelper.TestRoot, "certs/valid/server.key"),
+			path.Join(testhelper.TestRoot, "certs/invalid-path.key"),
+			path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+		},
+	}
+
+	cfg, err := newServerConfig(
+		&config.Config{GitlabUrl: "http://localhost", User: "user", Server: srvCfg},
+	)
+	require.NoError(t, err)
+
+	testCases := []struct {
+		desc          string
+		user          string
+		key           ssh.PublicKey
+		expectedError string
+	}{
+		{
+			desc:          "wrong user",
+			user:          "wrong-user",
+			key:           rsaPublicKey(t),
+			expectedError: "unknown user",
+		}, {
+			desc:          "prohibited dsa key",
+			user:          "user",
+			key:           dsaPublicKey(t),
+			expectedError: "DSA is prohibited",
+		}, {
+			desc:          "API error",
+			user:          "user",
+			key:           rsaPublicKey(t),
+			expectedError: "Internal API unreachable",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err = cfg.getAuthKey(context.Background(), tc.user, tc.key)
+			require.Error(t, err)
+			require.Equal(t, tc.expectedError, err.Error())
+		})
+	}
+}
+
+func rsaPublicKey(t *testing.T) ssh.PublicKey {
+	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	require.NoError(t, err)
+
+	publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
+	require.NoError(t, err)
+
+	return publicKey
+}
+
+func dsaPublicKey(t *testing.T) ssh.PublicKey {
+	privateKey := new(dsa.PrivateKey)
+	params := new(dsa.Parameters)
+	require.NoError(t, dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160))
+
+	privateKey.PublicKey.Parameters = *params
+	require.NoError(t, dsa.GenerateKey(privateKey, rand.Reader))
+
+	publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
+	require.NoError(t, err)
+
+	return publicKey
+}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -2,13 +2,9 @@
 
 import (
 	"context"
-	"encoding/base64"
-	"errors"
 	"fmt"
 	"net"
 	"net/http"
-	"os"
-	"strconv"
 	"sync"
 	"time"
 
@@ -16,7 +12,6 @@
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/log"
@@ -35,40 +30,20 @@
 type Server struct {
 	Config *config.Config
 
-	status               status
-	statusMu             sync.Mutex
-	wg                   sync.WaitGroup
-	listener             net.Listener
-	hostKeys             []ssh.Signer
-	authorizedKeysClient *authorizedkeys.Client
+	status       status
+	statusMu     sync.Mutex
+	wg           sync.WaitGroup
+	listener     net.Listener
+	serverConfig *serverConfig
 }
 
 func NewServer(cfg *config.Config) (*Server, error) {
-	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
+	serverConfig, err := newServerConfig(cfg)
 	if err != nil {
-		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+		return nil, err
 	}
 
-	var hostKeys []ssh.Signer
-	for _, filename := range cfg.Server.HostKeyFiles {
-		keyRaw, err := os.ReadFile(filename)
-		if err != nil {
-			log.WithError(err).Warnf("Failed to read host key %v", filename)
-			continue
-		}
-		key, err := ssh.ParsePrivateKey(keyRaw)
-		if err != nil {
-			log.WithError(err).Warnf("Failed to parse host key %v", filename)
-			continue
-		}
-
-		hostKeys = append(hostKeys, key)
-	}
-	if len(hostKeys) == 0 {
-		return nil, fmt.Errorf("No host keys could be loaded, aborting")
-	}
-
-	return &Server{Config: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
+	return &Server{Config: cfg, serverConfig: serverConfig}, nil
 }
 
 func (s *Server) ListenAndServe(ctx context.Context) error {
@@ -168,38 +143,6 @@
 	return s.status
 }
 
-func (s *Server) serverConfig(ctx context.Context) *ssh.ServerConfig {
-	sshCfg := &ssh.ServerConfig{
-		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
-			if conn.User() != s.Config.User {
-				return nil, errors.New("unknown user")
-			}
-			if key.Type() == ssh.KeyAlgoDSA {
-				return nil, errors.New("DSA is prohibited")
-			}
-			ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
-			defer cancel()
-			res, err := s.authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
-			if err != nil {
-				return nil, err
-			}
-
-			return &ssh.Permissions{
-				// Record the public key used for authentication.
-				Extensions: map[string]string{
-					"key-id": strconv.FormatInt(res.Id, 10),
-				},
-			}, nil
-		},
-	}
-
-	for _, key := range s.hostKeys {
-		sshCfg.AddHostKey(key)
-	}
-
-	return sshCfg
-}
-
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
 	remoteAddr := nconn.RemoteAddr().String()
 
@@ -216,7 +159,7 @@
 	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
 	defer cancel()
 
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig(ctx))
+	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
 		log.WithError(err).Info("Failed to initialize SSH connection")
 		return
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -104,13 +104,6 @@
 	require.Equal(t, 200, r.Result().StatusCode)
 }
 
-func TestNewServerWithoutHosts(t *testing.T) {
-	_, err := NewServer(&config.Config{GitlabUrl: "http://localhost"})
-
-	require.Error(t, err)
-	require.Equal(t, "No host keys could be loaded, aborting", err.Error())
-}
-
 func TestInvalidClientConfig(t *testing.T) {
 	setupServer(t)
 
@@ -120,6 +113,15 @@
 	require.Error(t, err)
 }
 
+func TestInvalidServerConfig(t *testing.T) {
+	s := &Server{Config: &config.Config{Server: config.ServerConfig{Listen: "invalid"}}}
+	err := s.ListenAndServe(context.Background())
+
+	require.Error(t, err)
+	require.Equal(t, "failed to listen for connection: listen tcp: address invalid: missing port in address", err.Error())
+	require.Nil(t, s.Shutdown())
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1631724701 0
#      Wed Sep 15 16:51:41 2021 +0000
# Node ID e92cc9bfe423906d35663bdc675dc4b30d76f9f8
# Parent  ae1afbe2d7390f14489d9c61baadc06fae4f8d51
# Parent  f48622b668b43d66466c50035198c262e66ab6eb
Merge branch 'id-sshd-test-3' into 'main'

Extract server config related code out of sshd.go

Closes #523

See merge request gitlab-org/gitlab-shell!523

diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/server_config.go
@@ -0,0 +1,94 @@
+package sshd
+
+import (
+	"context"
+	"encoding/base64"
+	"fmt"
+	"os"
+	"strconv"
+	"time"
+
+	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
+
+	"gitlab.com/gitlab-org/labkit/log"
+)
+
+type serverConfig struct {
+	cfg                  *config.Config
+	hostKeys             []ssh.Signer
+	authorizedKeysClient *authorizedkeys.Client
+}
+
+func newServerConfig(cfg *config.Config) (*serverConfig, error) {
+	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+	}
+
+	var hostKeys []ssh.Signer
+	for _, filename := range cfg.Server.HostKeyFiles {
+		keyRaw, err := os.ReadFile(filename)
+		if err != nil {
+			log.WithError(err).Warnf("Failed to read host key %v", filename)
+			continue
+		}
+		key, err := ssh.ParsePrivateKey(keyRaw)
+		if err != nil {
+			log.WithError(err).Warnf("Failed to parse host key %v", filename)
+			continue
+		}
+
+		hostKeys = append(hostKeys, key)
+	}
+	if len(hostKeys) == 0 {
+		return nil, fmt.Errorf("No host keys could be loaded, aborting")
+	}
+
+	return &serverConfig{cfg: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
+}
+
+func (s *serverConfig) getAuthKey(ctx context.Context, user string, key ssh.PublicKey) (*authorizedkeys.Response, error) {
+	if user != s.cfg.User {
+		return nil, fmt.Errorf("unknown user")
+	}
+	if key.Type() == ssh.KeyAlgoDSA {
+		return nil, fmt.Errorf("DSA is prohibited")
+	}
+
+	ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
+	defer cancel()
+
+	res, err := s.authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
+	if err != nil {
+		return nil, err
+	}
+
+	return res, nil
+}
+
+func (s *serverConfig) get(ctx context.Context) *ssh.ServerConfig {
+	sshCfg := &ssh.ServerConfig{
+		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
+			res, err := s.getAuthKey(ctx, conn.User(), key)
+			if err != nil {
+				return nil, err
+			}
+
+			return &ssh.Permissions{
+				// Record the public key used for authentication.
+				Extensions: map[string]string{
+					"key-id": strconv.FormatInt(res.Id, 10),
+				},
+			}, nil
+		},
+	}
+
+	for _, key := range s.hostKeys {
+		sshCfg.AddHostKey(key)
+	}
+
+	return sshCfg
+}
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/server_config_test.go
@@ -0,0 +1,105 @@
+package sshd
+
+import (
+	"context"
+	"crypto/dsa"
+	"crypto/rand"
+	"crypto/rsa"
+	"path"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+)
+
+func TestNewServerConfigWithoutHosts(t *testing.T) {
+	_, err := newServerConfig(&config.Config{GitlabUrl: "http://localhost"})
+
+	require.Error(t, err)
+	require.Equal(t, "No host keys could be loaded, aborting", err.Error())
+}
+
+func TestFailedAuthorizedKeysClient(t *testing.T) {
+	_, err := newServerConfig(&config.Config{GitlabUrl: "ftp://localhost"})
+
+	require.Error(t, err)
+	require.Equal(t, "failed to initialize GitLab client: Error creating http client: unknown GitLab URL prefix", err.Error())
+}
+
+func TestFailedGetAuthKey(t *testing.T) {
+	testhelper.PrepareTestRootDir(t)
+
+	srvCfg := config.ServerConfig{
+		Listen:                  "127.0.0.1",
+		ConcurrentSessionsLimit: 1,
+		HostKeyFiles: []string{
+			path.Join(testhelper.TestRoot, "certs/valid/server.key"),
+			path.Join(testhelper.TestRoot, "certs/invalid-path.key"),
+			path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+		},
+	}
+
+	cfg, err := newServerConfig(
+		&config.Config{GitlabUrl: "http://localhost", User: "user", Server: srvCfg},
+	)
+	require.NoError(t, err)
+
+	testCases := []struct {
+		desc          string
+		user          string
+		key           ssh.PublicKey
+		expectedError string
+	}{
+		{
+			desc:          "wrong user",
+			user:          "wrong-user",
+			key:           rsaPublicKey(t),
+			expectedError: "unknown user",
+		}, {
+			desc:          "prohibited dsa key",
+			user:          "user",
+			key:           dsaPublicKey(t),
+			expectedError: "DSA is prohibited",
+		}, {
+			desc:          "API error",
+			user:          "user",
+			key:           rsaPublicKey(t),
+			expectedError: "Internal API unreachable",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err = cfg.getAuthKey(context.Background(), tc.user, tc.key)
+			require.Error(t, err)
+			require.Equal(t, tc.expectedError, err.Error())
+		})
+	}
+}
+
+func rsaPublicKey(t *testing.T) ssh.PublicKey {
+	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	require.NoError(t, err)
+
+	publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
+	require.NoError(t, err)
+
+	return publicKey
+}
+
+func dsaPublicKey(t *testing.T) ssh.PublicKey {
+	privateKey := new(dsa.PrivateKey)
+	params := new(dsa.Parameters)
+	require.NoError(t, dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160))
+
+	privateKey.PublicKey.Parameters = *params
+	require.NoError(t, dsa.GenerateKey(privateKey, rand.Reader))
+
+	publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
+	require.NoError(t, err)
+
+	return publicKey
+}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -2,13 +2,9 @@
 
 import (
 	"context"
-	"encoding/base64"
-	"errors"
 	"fmt"
 	"net"
 	"net/http"
-	"os"
-	"strconv"
 	"sync"
 	"time"
 
@@ -16,7 +12,6 @@
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/log"
@@ -35,40 +30,20 @@
 type Server struct {
 	Config *config.Config
 
-	status               status
-	statusMu             sync.Mutex
-	wg                   sync.WaitGroup
-	listener             net.Listener
-	hostKeys             []ssh.Signer
-	authorizedKeysClient *authorizedkeys.Client
+	status       status
+	statusMu     sync.Mutex
+	wg           sync.WaitGroup
+	listener     net.Listener
+	serverConfig *serverConfig
 }
 
 func NewServer(cfg *config.Config) (*Server, error) {
-	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
+	serverConfig, err := newServerConfig(cfg)
 	if err != nil {
-		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+		return nil, err
 	}
 
-	var hostKeys []ssh.Signer
-	for _, filename := range cfg.Server.HostKeyFiles {
-		keyRaw, err := os.ReadFile(filename)
-		if err != nil {
-			log.WithError(err).Warnf("Failed to read host key %v", filename)
-			continue
-		}
-		key, err := ssh.ParsePrivateKey(keyRaw)
-		if err != nil {
-			log.WithError(err).Warnf("Failed to parse host key %v", filename)
-			continue
-		}
-
-		hostKeys = append(hostKeys, key)
-	}
-	if len(hostKeys) == 0 {
-		return nil, fmt.Errorf("No host keys could be loaded, aborting")
-	}
-
-	return &Server{Config: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
+	return &Server{Config: cfg, serverConfig: serverConfig}, nil
 }
 
 func (s *Server) ListenAndServe(ctx context.Context) error {
@@ -168,38 +143,6 @@
 	return s.status
 }
 
-func (s *Server) serverConfig(ctx context.Context) *ssh.ServerConfig {
-	sshCfg := &ssh.ServerConfig{
-		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
-			if conn.User() != s.Config.User {
-				return nil, errors.New("unknown user")
-			}
-			if key.Type() == ssh.KeyAlgoDSA {
-				return nil, errors.New("DSA is prohibited")
-			}
-			ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
-			defer cancel()
-			res, err := s.authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
-			if err != nil {
-				return nil, err
-			}
-
-			return &ssh.Permissions{
-				// Record the public key used for authentication.
-				Extensions: map[string]string{
-					"key-id": strconv.FormatInt(res.Id, 10),
-				},
-			}, nil
-		},
-	}
-
-	for _, key := range s.hostKeys {
-		sshCfg.AddHostKey(key)
-	}
-
-	return sshCfg
-}
-
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
 	remoteAddr := nconn.RemoteAddr().String()
 
@@ -216,7 +159,7 @@
 	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
 	defer cancel()
 
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig(ctx))
+	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
 		log.WithError(err).Info("Failed to initialize SSH connection")
 		return
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -104,13 +104,6 @@
 	require.Equal(t, 200, r.Result().StatusCode)
 }
 
-func TestNewServerWithoutHosts(t *testing.T) {
-	_, err := NewServer(&config.Config{GitlabUrl: "http://localhost"})
-
-	require.Error(t, err)
-	require.Equal(t, "No host keys could be loaded, aborting", err.Error())
-}
-
 func TestInvalidClientConfig(t *testing.T) {
 	setupServer(t)
 
@@ -120,6 +113,15 @@
 	require.Error(t, err)
 }
 
+func TestInvalidServerConfig(t *testing.T) {
+	s := &Server{Config: &config.Config{Server: config.ServerConfig{Listen: "invalid"}}}
+	err := s.ListenAndServe(context.Background())
+
+	require.Error(t, err)
+	require.Equal(t, "failed to listen for connection: listen tcp: address invalid: missing port in address", err.Error())
+	require.Nil(t, s.Shutdown())
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1631727960 -10800
#      Wed Sep 15 20:46:00 2021 +0300
# Node ID 54350589901ecd0d484ebeab99e6e66be2ea9ef1
# Parent  e92cc9bfe423906d35663bdc675dc4b30d76f9f8
Add context fields to logging

It adds correlation ids wherever possible

diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -97,7 +97,7 @@
 		sig := <-done
 		signal.Reset(syscall.SIGINT, syscall.SIGTERM)
 
-		log.WithFields(log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
+		log.WithContextFields(ctx, log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
 
 		server.Shutdown()
 
diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -64,7 +64,7 @@
 			"endpoint":     endpoint,
 		}
 
-		log.WithFields(fields).Info("Performing custom action")
+		log.WithContextFields(ctx, fields).Info("Performing custom action")
 
 		response, err := c.performRequest(ctx, client, endpoint, request)
 		if err != nil {
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -110,7 +110,7 @@
 	if serviceName == "" {
 		serviceName = "gitlab-shell-unknown"
 
-		log.WithFields(log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
+		log.WithContextFields(ctx, log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
 	}
 
 	serviceName = fmt.Sprintf("%s-%s", serviceName, gc.ServiceName)
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -54,7 +54,7 @@
 			// Prevent a panic in a single session from taking out the whole server
 			defer func() {
 				if err := recover(); err != nil {
-					log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", c.remoteAddr)
+					log.WithContextFields(ctx, log.Fields{"recovered_error": err, "address": c.remoteAddr}).Warn("panic handling session")
 				}
 			}()
 
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -32,12 +32,12 @@
 	for _, filename := range cfg.Server.HostKeyFiles {
 		keyRaw, err := os.ReadFile(filename)
 		if err != nil {
-			log.WithError(err).Warnf("Failed to read host key %v", filename)
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("Failed to read host key")
 			continue
 		}
 		key, err := ssh.ParsePrivateKey(keyRaw)
 		if err != nil {
-			log.WithError(err).Warnf("Failed to parse host key %v", filename)
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("Failed to parse host key")
 			continue
 		}
 
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -47,7 +47,7 @@
 }
 
 func (s *Server) ListenAndServe(ctx context.Context) error {
-	if err := s.listen(); err != nil {
+	if err := s.listen(ctx); err != nil {
 		return err
 	}
 	defer s.listener.Close()
@@ -85,7 +85,7 @@
 	return mux
 }
 
-func (s *Server) listen() error {
+func (s *Server) listen(ctx context.Context) error {
 	sshListener, err := net.Listen("tcp", s.Config.Server.Listen)
 	if err != nil {
 		return fmt.Errorf("failed to listen for connection: %w", err)
@@ -97,10 +97,10 @@
 			ReadHeaderTimeout: ProxyHeaderTimeout,
 		}
 
-		log.Info("Proxy protocol is enabled")
+		log.ContextLogger(ctx).Info("Proxy protocol is enabled")
 	}
 
-	log.WithFields(log.Fields{"tcp_address": sshListener.Addr().String()}).Info("Listening for SSH connections")
+	log.WithContextFields(ctx, log.Fields{"tcp_address": sshListener.Addr().String()}).Info("Listening for SSH connections")
 
 	s.listener = sshListener
 
@@ -117,7 +117,7 @@
 				break
 			}
 
-			log.WithError(err).Warn("Failed to accept connection")
+			log.ContextLogger(ctx).WithError(err).Warn("Failed to accept connection")
 			continue
 		}
 
@@ -152,7 +152,7 @@
 	// Prevent a panic in a single connection from taking out the whole server
 	defer func() {
 		if err := recover(); err != nil {
-			log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", remoteAddr)
+			log.WithContextFields(ctx, log.Fields{"recovered_error": err, "address": remoteAddr}).Warn("panic handling session")
 		}
 	}()
 
@@ -161,7 +161,7 @@
 
 	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
-		log.WithError(err).Info("Failed to initialize SSH connection")
+		log.ContextLogger(ctx).WithError(err).Info("Failed to initialize SSH connection")
 		return
 	}
 
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1631758488 0
#      Thu Sep 16 02:14:48 2021 +0000
# Node ID ddc4c0b005d1fc267d891f99b2fb3e4c2a470a47
# Parent  e92cc9bfe423906d35663bdc675dc4b30d76f9f8
# Parent  54350589901ecd0d484ebeab99e6e66be2ea9ef1
Merge branch 'id-context-fields' into 'main'

Add context fields to logging

See merge request gitlab-org/gitlab-shell!524

diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -97,7 +97,7 @@
 		sig := <-done
 		signal.Reset(syscall.SIGINT, syscall.SIGTERM)
 
-		log.WithFields(log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
+		log.WithContextFields(ctx, log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
 
 		server.Shutdown()
 
diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -64,7 +64,7 @@
 			"endpoint":     endpoint,
 		}
 
-		log.WithFields(fields).Info("Performing custom action")
+		log.WithContextFields(ctx, fields).Info("Performing custom action")
 
 		response, err := c.performRequest(ctx, client, endpoint, request)
 		if err != nil {
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -110,7 +110,7 @@
 	if serviceName == "" {
 		serviceName = "gitlab-shell-unknown"
 
-		log.WithFields(log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
+		log.WithContextFields(ctx, log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
 	}
 
 	serviceName = fmt.Sprintf("%s-%s", serviceName, gc.ServiceName)
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -54,7 +54,7 @@
 			// Prevent a panic in a single session from taking out the whole server
 			defer func() {
 				if err := recover(); err != nil {
-					log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", c.remoteAddr)
+					log.WithContextFields(ctx, log.Fields{"recovered_error": err, "address": c.remoteAddr}).Warn("panic handling session")
 				}
 			}()
 
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -32,12 +32,12 @@
 	for _, filename := range cfg.Server.HostKeyFiles {
 		keyRaw, err := os.ReadFile(filename)
 		if err != nil {
-			log.WithError(err).Warnf("Failed to read host key %v", filename)
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("Failed to read host key")
 			continue
 		}
 		key, err := ssh.ParsePrivateKey(keyRaw)
 		if err != nil {
-			log.WithError(err).Warnf("Failed to parse host key %v", filename)
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("Failed to parse host key")
 			continue
 		}
 
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -47,7 +47,7 @@
 }
 
 func (s *Server) ListenAndServe(ctx context.Context) error {
-	if err := s.listen(); err != nil {
+	if err := s.listen(ctx); err != nil {
 		return err
 	}
 	defer s.listener.Close()
@@ -85,7 +85,7 @@
 	return mux
 }
 
-func (s *Server) listen() error {
+func (s *Server) listen(ctx context.Context) error {
 	sshListener, err := net.Listen("tcp", s.Config.Server.Listen)
 	if err != nil {
 		return fmt.Errorf("failed to listen for connection: %w", err)
@@ -97,10 +97,10 @@
 			ReadHeaderTimeout: ProxyHeaderTimeout,
 		}
 
-		log.Info("Proxy protocol is enabled")
+		log.ContextLogger(ctx).Info("Proxy protocol is enabled")
 	}
 
-	log.WithFields(log.Fields{"tcp_address": sshListener.Addr().String()}).Info("Listening for SSH connections")
+	log.WithContextFields(ctx, log.Fields{"tcp_address": sshListener.Addr().String()}).Info("Listening for SSH connections")
 
 	s.listener = sshListener
 
@@ -117,7 +117,7 @@
 				break
 			}
 
-			log.WithError(err).Warn("Failed to accept connection")
+			log.ContextLogger(ctx).WithError(err).Warn("Failed to accept connection")
 			continue
 		}
 
@@ -152,7 +152,7 @@
 	// Prevent a panic in a single connection from taking out the whole server
 	defer func() {
 		if err := recover(); err != nil {
-			log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", remoteAddr)
+			log.WithContextFields(ctx, log.Fields{"recovered_error": err, "address": remoteAddr}).Warn("panic handling session")
 		}
 	}()
 
@@ -161,7 +161,7 @@
 
 	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
-		log.WithError(err).Info("Failed to initialize SSH connection")
+		log.ContextLogger(ctx).WithError(err).Info("Failed to initialize SSH connection")
 		return
 	}
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1631892599 -10800
#      Fri Sep 17 18:29:59 2021 +0300
# Node ID 9e1d48c2a47f81c3a3da7bb700b740141dbed88b
# Parent  ddc4c0b005d1fc267d891f99b2fb3e4c2a470a47
Improve err message given when Gitaly unavailable

diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -9,7 +9,9 @@
 	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
 	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
 	"google.golang.org/grpc"
+	grpccodes "google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
+	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
@@ -50,6 +52,12 @@
 	childCtx := withOutgoingMetadata(ctx, gc.Features)
 	_, err = handler(childCtx, conn)
 
+	if err != nil && grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
+		log.WithError(err).Error("Gitaly is unavailable")
+
+		return fmt.Errorf("Git service is temporarily unavailable")
+	}
+
 	return err
 }
 
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -7,7 +7,9 @@
 
 	"github.com/stretchr/testify/require"
 	"google.golang.org/grpc"
+	grpccodes "google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
+	grpcstatus "google.golang.org/grpc/status"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -45,6 +47,18 @@
 	require.EqualError(t, err, "no gitaly_address given")
 }
 
+func TestUnavailableGitalyErr(t *testing.T) {
+	cmd := GitalyCommand{
+		Config:  &config.Config{},
+		Address: "tcp://localhost:9999",
+	}
+
+	expectedErr := grpcstatus.Error(grpccodes.Unavailable, "error")
+	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, expectedErr))
+
+	require.EqualError(t, err, "Git service is temporarily unavailable")
+}
+
 func TestRunGitalyCommandMetadata(t *testing.T) {
 	tests := []struct {
 		name string
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1631895324 0
#      Fri Sep 17 16:15:24 2021 +0000
# Node ID 99e9c050b9a714d6ec0848982435c48589c4b1e3
# Parent  ddc4c0b005d1fc267d891f99b2fb3e4c2a470a47
# Parent  9e1d48c2a47f81c3a3da7bb700b740141dbed88b
Merge branch 'id-clean-up-unavailable-message' into 'main'

Improve err message given when Gitaly unavailable

Closes gitlab#340819

See merge request gitlab-org/gitlab-shell!526

diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -9,7 +9,9 @@
 	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
 	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
 	"google.golang.org/grpc"
+	grpccodes "google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
+	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
@@ -50,6 +52,12 @@
 	childCtx := withOutgoingMetadata(ctx, gc.Features)
 	_, err = handler(childCtx, conn)
 
+	if err != nil && grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
+		log.WithError(err).Error("Gitaly is unavailable")
+
+		return fmt.Errorf("Git service is temporarily unavailable")
+	}
+
 	return err
 }
 
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -7,7 +7,9 @@
 
 	"github.com/stretchr/testify/require"
 	"google.golang.org/grpc"
+	grpccodes "google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
+	grpcstatus "google.golang.org/grpc/status"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -45,6 +47,18 @@
 	require.EqualError(t, err, "no gitaly_address given")
 }
 
+func TestUnavailableGitalyErr(t *testing.T) {
+	cmd := GitalyCommand{
+		Config:  &config.Config{},
+		Address: "tcp://localhost:9999",
+	}
+
+	expectedErr := grpcstatus.Error(grpccodes.Unavailable, "error")
+	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, expectedErr))
+
+	require.EqualError(t, err, "Git service is temporarily unavailable")
+}
+
 func TestRunGitalyCommandMetadata(t *testing.T) {
 	tests := []struct {
 		name string
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1632125981 0
#      Mon Sep 20 08:19:41 2021 +0000
# Node ID 713b025d4a396f053c4939a6010c7502c6bb8afa
# Parent  ddc4c0b005d1fc267d891f99b2fb3e4c2a470a47
refactor: unify instantiation of command.Shell

diff --git a/cmd/gitlab-shell/command/command.go b/cmd/gitlab-shell/command/command.go
--- a/cmd/gitlab-shell/command/command.go
+++ b/cmd/gitlab-shell/command/command.go
@@ -30,6 +30,20 @@
 	return nil, disallowedcommand.Error
 }
 
+func NewWithKey(gitlabKeyId string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(nil, env)
+	if err != nil {
+		return nil, err
+	}
+
+	args.GitlabKeyId = gitlabKeyId
+	if cmd := Build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
 func Parse(arguments []string, env sshenv.Env) (*commandargs.Shell, error) {
 	args := &commandargs.Shell{Arguments: arguments, Env: env}
 
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -2,13 +2,14 @@
 
 import (
 	"context"
+	"errors"
 	"fmt"
 
 	"golang.org/x/crypto/ssh"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
@@ -104,19 +105,11 @@
 		req.Reply(true, []byte{})
 	}
 
-	args := &commandargs.Shell{
-		GitlabKeyId: s.gitlabKeyId,
-		Env: sshenv.Env{
-			IsSSHConnection:    true,
-			OriginalCommand:    s.execCmd,
-			GitProtocolVersion: s.gitProtocolVersion,
-			RemoteAddr:         s.remoteAddr,
-		},
-	}
-
-	if err := args.ParseCommand(s.execCmd); err != nil {
-		s.toStderr("Failed to parse command: %v\n", err.Error())
-		return 128
+	env := sshenv.Env{
+		IsSSHConnection:    true,
+		OriginalCommand:    s.execCmd,
+		GitProtocolVersion: s.gitProtocolVersion,
+		RemoteAddr:         s.remoteAddr,
 	}
 
 	rw := &readwriter.ReadWriter{
@@ -125,9 +118,13 @@
 		ErrOut: s.channel.Stderr(),
 	}
 
-	cmd := shellCmd.Build(args, s.cfg, rw)
-	if cmd == nil {
-		s.toStderr("Unknown command: %v\n", args.CommandType)
+	cmd, err := shellCmd.NewWithKey(s.gitlabKeyId, env, s.cfg, rw)
+
+	if err != nil {
+		if !errors.Is(err, disallowedcommand.Error) {
+			s.toStderr("Failed to parse command: %v\n", err.Error())
+		}
+		s.toStderr("Unknown command: %v\n", s.execCmd)
 		return 128
 	}
 
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -145,7 +145,7 @@
 		{
 			desc:             "fails to parse command",
 			cmd:              `\`,
-			errMsg:           "Failed to parse command: invalid command line string\n",
+			errMsg:           "Failed to parse command: Invalid SSH command\nUnknown command: \\\n",
 			gitlabKeyId:      "root",
 			expectedExitCode: 128,
 		}, {
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1632125982 0
#      Mon Sep 20 08:19:42 2021 +0000
# Node ID 2a7a4328f7ff1125158ea4c13c4d7a7625c96e3d
# Parent  99e9c050b9a714d6ec0848982435c48589c4b1e3
# Parent  713b025d4a396f053c4939a6010c7502c6bb8afa
Merge branch 'refactor/unify-shell' into 'main'

refactor: unify instantiation of command.Shell

Closes #517

See merge request gitlab-org/gitlab-shell!520

diff --git a/cmd/gitlab-shell/command/command.go b/cmd/gitlab-shell/command/command.go
--- a/cmd/gitlab-shell/command/command.go
+++ b/cmd/gitlab-shell/command/command.go
@@ -30,6 +30,20 @@
 	return nil, disallowedcommand.Error
 }
 
+func NewWithKey(gitlabKeyId string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(nil, env)
+	if err != nil {
+		return nil, err
+	}
+
+	args.GitlabKeyId = gitlabKeyId
+	if cmd := Build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
 func Parse(arguments []string, env sshenv.Env) (*commandargs.Shell, error) {
 	args := &commandargs.Shell{Arguments: arguments, Env: env}
 
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -2,13 +2,14 @@
 
 import (
 	"context"
+	"errors"
 	"fmt"
 
 	"golang.org/x/crypto/ssh"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
@@ -104,19 +105,11 @@
 		req.Reply(true, []byte{})
 	}
 
-	args := &commandargs.Shell{
-		GitlabKeyId: s.gitlabKeyId,
-		Env: sshenv.Env{
-			IsSSHConnection:    true,
-			OriginalCommand:    s.execCmd,
-			GitProtocolVersion: s.gitProtocolVersion,
-			RemoteAddr:         s.remoteAddr,
-		},
-	}
-
-	if err := args.ParseCommand(s.execCmd); err != nil {
-		s.toStderr("Failed to parse command: %v\n", err.Error())
-		return 128
+	env := sshenv.Env{
+		IsSSHConnection:    true,
+		OriginalCommand:    s.execCmd,
+		GitProtocolVersion: s.gitProtocolVersion,
+		RemoteAddr:         s.remoteAddr,
 	}
 
 	rw := &readwriter.ReadWriter{
@@ -125,9 +118,13 @@
 		ErrOut: s.channel.Stderr(),
 	}
 
-	cmd := shellCmd.Build(args, s.cfg, rw)
-	if cmd == nil {
-		s.toStderr("Unknown command: %v\n", args.CommandType)
+	cmd, err := shellCmd.NewWithKey(s.gitlabKeyId, env, s.cfg, rw)
+
+	if err != nil {
+		if !errors.Is(err, disallowedcommand.Error) {
+			s.toStderr("Failed to parse command: %v\n", err.Error())
+		}
+		s.toStderr("Unknown command: %v\n", s.execCmd)
 		return 128
 	}
 
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -145,7 +145,7 @@
 		{
 			desc:             "fails to parse command",
 			cmd:              `\`,
-			errMsg:           "Failed to parse command: invalid command line string\n",
+			errMsg:           "Failed to parse command: Invalid SSH command\nUnknown command: \\\n",
 			gitlabKeyId:      "root",
 			expectedExitCode: 128,
 		}, {
# HG changeset patch
# User Kevin <gitlab@a.ikke.info>
# Date 1631813846 0
#      Thu Sep 16 17:37:26 2021 +0000
# Node ID 40a49942db1f297590dad05f5a92ba7b1216e9b8
# Parent  ddc4c0b005d1fc267d891f99b2fb3e4c2a470a47
makefile: properly quote '$' in VERSION_STRING

If git is not available or gitlab-shell is not built in a repository, it falls back the VERSION file. That command is not properly escaped and results in the message:

> awk: cmd. line:1: Unexpected token

When you remove the `2>/dev/null`. Escape the '$' characters to solve this.

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 .PHONY: validate verify verify_ruby verify_golang test test_ruby test_golang coverage coverage_golang setup _script_install build compile check clean install
 
 GO_SOURCES := $(shell find . -name '*.go')
-VERSION_STRING := $(shell git describe --match v* 2>/dev/null || awk '$0="v"$0' VERSION 2>/dev/null || echo unknown)
+VERSION_STRING := $(shell git describe --match v* 2>/dev/null || awk '$$0="v"$$0' VERSION 2>/dev/null || echo unknown)
 BUILD_TIME := $(shell date -u +%Y%m%d.%H%M%S)
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)"
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1632130317 0
#      Mon Sep 20 09:31:57 2021 +0000
# Node ID 9c4148e7ed0ba75d315ab0f7befcc1e44c9df687
# Parent  2a7a4328f7ff1125158ea4c13c4d7a7625c96e3d
# Parent  40a49942db1f297590dad05f5a92ba7b1216e9b8
Merge branch 'fix-makefile-version-string' into 'main'

makefile: properly escape '$' in VERSION_STRING

See merge request gitlab-org/gitlab-shell!525

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 .PHONY: validate verify verify_ruby verify_golang test test_ruby test_golang coverage coverage_golang setup _script_install build compile check clean install
 
 GO_SOURCES := $(shell find . -name '*.go')
-VERSION_STRING := $(shell git describe --match v* 2>/dev/null || awk '$0="v"$0' VERSION 2>/dev/null || echo unknown)
+VERSION_STRING := $(shell git describe --match v* 2>/dev/null || awk '$$0="v"$$0' VERSION 2>/dev/null || echo unknown)
 BUILD_TIME := $(shell date -u +%Y%m%d.%H%M%S)
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)"
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1632379994 25200
#      Wed Sep 22 23:53:14 2021 -0700
# Node ID ca40e9ceffb023c7c4bb864a9890194ad0f89297
# Parent  9c4148e7ed0ba75d315ab0f7befcc1e44c9df687
Only validate SSL cert file exists if a value is supplied

This fixes a regression in
https://gitlab.com/gitlab-org/gitlab-shell/-/merge_requests/508. If an
HTTPS internal API URL were used, gitlab-shell would not work at all. We
now handle blank `caFile` properly.

Relates to https://gitlab.com/gitlab-org/gitlab-shell/-/issues/529

diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -54,6 +54,22 @@
 	}
 }
 
+func validateCaFile(filename string) error {
+	if filename == "" {
+		return nil
+	}
+
+	if _, err := os.Stat(filename); err != nil {
+		if os.IsNotExist(err) {
+			return fmt.Errorf("cannot find cafile '%s': %w", filename, ErrCafileNotFound)
+		}
+
+		return err
+	}
+
+	return nil
+}
+
 // Deprecated: use NewHTTPClientWithOpts - https://gitlab.com/gitlab-org/gitlab-shell/-/issues/484
 func NewHTTPClient(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64) *HttpClient {
 	c, err := NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath, selfSignedCert, readTimeoutSeconds, nil)
@@ -73,10 +89,8 @@
 	} else if strings.HasPrefix(gitlabURL, httpProtocol) {
 		transport, host = buildHttpTransport(gitlabURL)
 	} else if strings.HasPrefix(gitlabURL, httpsProtocol) {
-		if _, err := os.Stat(caFile); err != nil {
-			if os.IsNotExist(err) {
-				return nil, fmt.Errorf("cannot find cafile '%s': %w", caFile, ErrCafileNotFound)
-			}
+		err = validateCaFile(caFile)
+		if err != nil {
 			return nil, err
 		}
 
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -66,10 +66,11 @@
 
 func TestFailedRequests(t *testing.T) {
 	testCases := []struct {
-		desc          string
-		caFile        string
-		caPath        string
-		expectedError string
+		desc                   string
+		caFile                 string
+		caPath                 string
+		expectedCaFileNotFound bool
+		expectedError          string
 	}{
 		{
 			desc:          "Invalid CaFile",
@@ -77,18 +78,25 @@
 			expectedError: "Internal API unreachable",
 		},
 		{
-			desc:   "Invalid CaPath",
-			caPath: path.Join(testhelper.TestRoot, "certs/invalid"),
+			desc:                   "Missing CaFile",
+			caFile:                 path.Join(testhelper.TestRoot, "certs/invalid/missing.crt"),
+			expectedCaFileNotFound: true,
 		},
 		{
-			desc: "Empty config",
+			desc:          "Invalid CaPath",
+			caPath:        path.Join(testhelper.TestRoot, "certs/invalid"),
+			expectedError: "Internal API unreachable",
+		},
+		{
+			desc:          "Empty config",
+			expectedError: "Internal API unreachable",
 		},
 	}
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
 			client, err := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "", false)
-			if tc.caFile == "" {
+			if tc.expectedCaFileNotFound {
 				require.Error(t, err)
 				require.ErrorIs(t, err, ErrCafileNotFound)
 			} else {
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1632383296 0
#      Thu Sep 23 07:48:16 2021 +0000
# Node ID 131fe8c9ab76327e97ed8f89d5b269de0300e758
# Parent  9c4148e7ed0ba75d315ab0f7befcc1e44c9df687
# Parent  ca40e9ceffb023c7c4bb864a9890194ad0f89297
Merge branch 'sh-fix-issue-529' into 'main'

Only validate SSL cert file exists if a value is supplied

See merge request gitlab-org/gitlab-shell!527

diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -54,6 +54,22 @@
 	}
 }
 
+func validateCaFile(filename string) error {
+	if filename == "" {
+		return nil
+	}
+
+	if _, err := os.Stat(filename); err != nil {
+		if os.IsNotExist(err) {
+			return fmt.Errorf("cannot find cafile '%s': %w", filename, ErrCafileNotFound)
+		}
+
+		return err
+	}
+
+	return nil
+}
+
 // Deprecated: use NewHTTPClientWithOpts - https://gitlab.com/gitlab-org/gitlab-shell/-/issues/484
 func NewHTTPClient(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64) *HttpClient {
 	c, err := NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath, selfSignedCert, readTimeoutSeconds, nil)
@@ -73,10 +89,8 @@
 	} else if strings.HasPrefix(gitlabURL, httpProtocol) {
 		transport, host = buildHttpTransport(gitlabURL)
 	} else if strings.HasPrefix(gitlabURL, httpsProtocol) {
-		if _, err := os.Stat(caFile); err != nil {
-			if os.IsNotExist(err) {
-				return nil, fmt.Errorf("cannot find cafile '%s': %w", caFile, ErrCafileNotFound)
-			}
+		err = validateCaFile(caFile)
+		if err != nil {
 			return nil, err
 		}
 
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -66,10 +66,11 @@
 
 func TestFailedRequests(t *testing.T) {
 	testCases := []struct {
-		desc          string
-		caFile        string
-		caPath        string
-		expectedError string
+		desc                   string
+		caFile                 string
+		caPath                 string
+		expectedCaFileNotFound bool
+		expectedError          string
 	}{
 		{
 			desc:          "Invalid CaFile",
@@ -77,18 +78,25 @@
 			expectedError: "Internal API unreachable",
 		},
 		{
-			desc:   "Invalid CaPath",
-			caPath: path.Join(testhelper.TestRoot, "certs/invalid"),
+			desc:                   "Missing CaFile",
+			caFile:                 path.Join(testhelper.TestRoot, "certs/invalid/missing.crt"),
+			expectedCaFileNotFound: true,
 		},
 		{
-			desc: "Empty config",
+			desc:          "Invalid CaPath",
+			caPath:        path.Join(testhelper.TestRoot, "certs/invalid"),
+			expectedError: "Internal API unreachable",
+		},
+		{
+			desc:          "Empty config",
+			expectedError: "Internal API unreachable",
 		},
 	}
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
 			client, err := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "", false)
-			if tc.caFile == "" {
+			if tc.expectedCaFileNotFound {
 				require.Error(t, err)
 				require.ErrorIs(t, err, ErrCafileNotFound)
 			} else {
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1632390072 -3600
#      Thu Sep 23 10:41:12 2021 +0100
# Node ID c6a89c8f8be2355f1fc80befc68a9aa7ada06d75
# Parent  131fe8c9ab76327e97ed8f89d5b269de0300e758
Respect log-level configuration again

This was lost in the move from Ruby to Go. Restore it now.

Changelog: fixed

diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -46,6 +46,7 @@
 	RootDir               string
 	LogFile               string `yaml:"log_file,omitempty"`
 	LogFormat             string `yaml:"log_format,omitempty"`
+	LogLevel              string `yaml:"log_level,omitempty"`
 	GitlabUrl             string `yaml:"gitlab_url"`
 	GitlabRelativeURLRoot string `yaml:"gitlab_relative_url_root"`
 	GitlabTracing         string `yaml:"gitlab_tracing"`
@@ -66,6 +67,7 @@
 	DefaultConfig = Config{
 		LogFile:   "gitlab-shell.log",
 		LogFormat: "json",
+		LogLevel:  "info",
 		Server:    DefaultServerConfig,
 		User:      "git",
 	}
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -35,6 +35,7 @@
 		log.WithFormatter(logFmt(cfg.LogFormat)),
 		log.WithOutputName(logFile(cfg.LogFile)),
 		log.WithTimezone(time.UTC),
+		log.WithLogLevel(cfg.LogLevel),
 	}
 }
 
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -3,7 +3,6 @@
 import (
 	"os"
 	"regexp"
-	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -26,12 +25,37 @@
 	defer closer.Close()
 
 	log.Info("this is a test")
+	log.WithFields(log.Fields{}).Debug("debug log message")
 
 	tmpFile.Close()
 
 	data, err := os.ReadFile(tmpFile.Name())
 	require.NoError(t, err)
-	require.True(t, strings.Contains(string(data), `msg":"this is a test"`))
+	require.Contains(t, string(data), `msg":"this is a test"`)
+	require.NotContains(t, string(data), `msg:":"debug log message"`)
+}
+
+func TestConfigureWithDebugLogLevel(t *testing.T) {
+	tmpFile, err := os.CreateTemp(os.TempDir(), "logtest-")
+	require.NoError(t, err)
+	defer tmpFile.Close()
+
+	config := config.Config{
+		LogFile:   tmpFile.Name(),
+		LogFormat: "json",
+		LogLevel:  "debug",
+	}
+
+	closer := Configure(&config)
+	defer closer.Close()
+
+	log.WithFields(log.Fields{}).Debug("debug log message")
+
+	tmpFile.Close()
+
+	data, err := os.ReadFile(tmpFile.Name())
+	require.NoError(t, err)
+	require.Contains(t, string(data), `msg":"debug log message"`)
 }
 
 func TestConfigureWithPermissionError(t *testing.T) {
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1632394113 0
#      Thu Sep 23 10:48:33 2021 +0000
# Node ID 1638ae747fdece49dba9df3396ce3cbb2bd5d3e1
# Parent  131fe8c9ab76327e97ed8f89d5b269de0300e758
# Parent  c6a89c8f8be2355f1fc80befc68a9aa7ada06d75
Merge branch '502-restore-log-level-config' into 'main'

Respect log-level configuration again

Closes #502

See merge request gitlab-org/gitlab-shell!530

diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -46,6 +46,7 @@
 	RootDir               string
 	LogFile               string `yaml:"log_file,omitempty"`
 	LogFormat             string `yaml:"log_format,omitempty"`
+	LogLevel              string `yaml:"log_level,omitempty"`
 	GitlabUrl             string `yaml:"gitlab_url"`
 	GitlabRelativeURLRoot string `yaml:"gitlab_relative_url_root"`
 	GitlabTracing         string `yaml:"gitlab_tracing"`
@@ -66,6 +67,7 @@
 	DefaultConfig = Config{
 		LogFile:   "gitlab-shell.log",
 		LogFormat: "json",
+		LogLevel:  "info",
 		Server:    DefaultServerConfig,
 		User:      "git",
 	}
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -35,6 +35,7 @@
 		log.WithFormatter(logFmt(cfg.LogFormat)),
 		log.WithOutputName(logFile(cfg.LogFile)),
 		log.WithTimezone(time.UTC),
+		log.WithLogLevel(cfg.LogLevel),
 	}
 }
 
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -3,7 +3,6 @@
 import (
 	"os"
 	"regexp"
-	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -26,12 +25,37 @@
 	defer closer.Close()
 
 	log.Info("this is a test")
+	log.WithFields(log.Fields{}).Debug("debug log message")
 
 	tmpFile.Close()
 
 	data, err := os.ReadFile(tmpFile.Name())
 	require.NoError(t, err)
-	require.True(t, strings.Contains(string(data), `msg":"this is a test"`))
+	require.Contains(t, string(data), `msg":"this is a test"`)
+	require.NotContains(t, string(data), `msg:":"debug log message"`)
+}
+
+func TestConfigureWithDebugLogLevel(t *testing.T) {
+	tmpFile, err := os.CreateTemp(os.TempDir(), "logtest-")
+	require.NoError(t, err)
+	defer tmpFile.Close()
+
+	config := config.Config{
+		LogFile:   tmpFile.Name(),
+		LogFormat: "json",
+		LogLevel:  "debug",
+	}
+
+	closer := Configure(&config)
+	defer closer.Close()
+
+	log.WithFields(log.Fields{}).Debug("debug log message")
+
+	tmpFile.Close()
+
+	data, err := os.ReadFile(tmpFile.Name())
+	require.NoError(t, err)
+	require.Contains(t, string(data), `msg":"debug log message"`)
 }
 
 func TestConfigureWithPermissionError(t *testing.T) {
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1632767286 -3600
#      Mon Sep 27 19:28:06 2021 +0100
# Node ID 311bf5c473bb629dd86aa87884931fdd277c0f51
# Parent  1638ae747fdece49dba9df3396ce3cbb2bd5d3e1
Don't swallow an error parsing SSH_ORIGINAL_COMMAND

diff --git a/cmd/gitlab-shell/command/command_test.go b/cmd/gitlab-shell/command/command_test.go
--- a/cmd/gitlab-shell/command/command_test.go
+++ b/cmd/gitlab-shell/command/command_test.go
@@ -267,7 +267,7 @@
 			executable:    &executable.Executable{Name: executable.GitlabShell},
 			env:           sshenv.Env{IsSSHConnection: true, OriginalCommand: `git receive-pack "`},
 			arguments:     []string{},
-			expectedError: "Invalid SSH command",
+			expectedError: "Invalid SSH command: invalid command line string",
 		},
 	}
 
diff --git a/internal/command/commandargs/shell.go b/internal/command/commandargs/shell.go
--- a/internal/command/commandargs/shell.go
+++ b/internal/command/commandargs/shell.go
@@ -1,7 +1,7 @@
 package commandargs
 
 import (
-	"errors"
+	"fmt"
 	"regexp"
 
 	"github.com/mattn/go-shellwords"
@@ -49,21 +49,16 @@
 
 func (s *Shell) validate() error {
 	if !s.Env.IsSSHConnection {
-		return errors.New("Only SSH allowed")
+		return fmt.Errorf("Only SSH allowed")
 	}
 
-	if !s.isValidSSHCommand() {
-		return errors.New("Invalid SSH command")
+	if err := s.ParseCommand(s.Env.OriginalCommand); err != nil {
+		return fmt.Errorf("Invalid SSH command: %w", err)
 	}
 
 	return nil
 }
 
-func (s *Shell) isValidSSHCommand() bool {
-	err := s.ParseCommand(s.Env.OriginalCommand)
-	return err == nil
-}
-
 func (s *Shell) parseWho() {
 	for _, argument := range s.Arguments {
 		if keyId := tryParseKeyId(argument); keyId != "" {
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -145,7 +145,7 @@
 		{
 			desc:             "fails to parse command",
 			cmd:              `\`,
-			errMsg:           "Failed to parse command: Invalid SSH command\nUnknown command: \\\n",
+			errMsg:           "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
 			gitlabKeyId:      "root",
 			expectedExitCode: 128,
 		}, {
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1632767306 -3600
#      Mon Sep 27 19:28:26 2021 +0100
# Node ID b582b99b4d02e9fc445bb6efe7cb760752a11e03
# Parent  311bf5c473bb629dd86aa87884931fdd277c0f51
Add debug logging to gitlab-sshd session

diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -5,6 +5,7 @@
 	"errors"
 	"fmt"
 
+	"gitlab.com/gitlab-org/labkit/log"
 	"golang.org/x/crypto/ssh"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
@@ -40,16 +41,27 @@
 }
 
 func (s *session) handle(ctx context.Context, requests <-chan *ssh.Request) {
+	ctxlog := log.ContextLogger(ctx)
+
+	ctxlog.Debug("session: handle: entering request loop")
+
 	for req := range requests {
+		sessionLog := ctxlog.WithFields(log.Fields{
+			"bytesize":   len(req.Payload),
+			"type":       req.Type,
+			"want_reply": req.WantReply,
+		})
+		sessionLog.Debug("session: handle: request received")
+
 		var shouldContinue bool
 		switch req.Type {
 		case "env":
-			shouldContinue = s.handleEnv(req)
+			shouldContinue = s.handleEnv(ctx, req)
 		case "exec":
 			shouldContinue = s.handleExec(ctx, req)
 		case "shell":
 			shouldContinue = false
-			s.exit(s.handleShell(ctx, req))
+			s.exit(ctx, s.handleShell(ctx, req))
 		default:
 			// Ignore unknown requests but don't terminate the session
 			shouldContinue = true
@@ -58,18 +70,23 @@
 			}
 		}
 
+		sessionLog.WithField("should_continue", shouldContinue).Debug("session: handle: request processed")
+
 		if !shouldContinue {
 			s.channel.Close()
 			break
 		}
 	}
+
+	ctxlog.Debug("session: handle: exiting request loop")
 }
 
-func (s *session) handleEnv(req *ssh.Request) bool {
+func (s *session) handleEnv(ctx context.Context, req *ssh.Request) bool {
 	var accepted bool
 	var envRequest envRequest
 
 	if err := ssh.Unmarshal(req.Payload, &envRequest); err != nil {
+		log.ContextLogger(ctx).WithError(err).Error("session: handleEnv: failed to unmarshal request")
 		return false
 	}
 
@@ -85,6 +102,10 @@
 		req.Reply(accepted, []byte{})
 	}
 
+	log.WithContextFields(
+		ctx, log.Fields{"accepted": accepted, "env_request": envRequest},
+	).Debug("session: handleEnv: processed")
+
 	return true
 }
 
@@ -96,7 +117,8 @@
 
 	s.execCmd = execRequest.Command
 
-	s.exit(s.handleShell(ctx, req))
+	s.exit(ctx, s.handleShell(ctx, req))
+
 	return false
 }
 
@@ -119,28 +141,30 @@
 	}
 
 	cmd, err := shellCmd.NewWithKey(s.gitlabKeyId, env, s.cfg, rw)
-
 	if err != nil {
 		if !errors.Is(err, disallowedcommand.Error) {
-			s.toStderr("Failed to parse command: %v\n", err.Error())
+			s.toStderr(ctx, "Failed to parse command: %v\n", err.Error())
 		}
-		s.toStderr("Unknown command: %v\n", s.execCmd)
+		s.toStderr(ctx, "Unknown command: %v\n", s.execCmd)
 		return 128
 	}
 
 	if err := cmd.Execute(ctx); err != nil {
-		s.toStderr("remote: ERROR: %v\n", err.Error())
+		s.toStderr(ctx, "remote: ERROR: %v\n", err.Error())
 		return 1
 	}
 
 	return 0
 }
 
-func (s *session) toStderr(format string, args ...interface{}) {
-	fmt.Fprintf(s.channel.Stderr(), format, args...)
+func (s *session) toStderr(ctx context.Context, format string, args ...interface{}) {
+	out := fmt.Sprintf(format, args...)
+	log.WithContextFields(ctx, log.Fields{"stderr": out}).Debug("session: toStderr: output")
+	fmt.Fprint(s.channel.Stderr(), out)
 }
 
-func (s *session) exit(status uint32) {
+func (s *session) exit(ctx context.Context, status uint32) {
+	log.WithContextFields(ctx, log.Fields{"exit_status": status}).Info("session: exit: exiting")
 	req := exitStatusReq{ExitStatus: status}
 
 	s.channel.CloseWrite()
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -86,7 +86,7 @@
 			s := &session{gitProtocolVersion: "1"}
 			r := &ssh.Request{Payload: tc.payload}
 
-			require.Equal(t, s.handleEnv(r), tc.expectedResult)
+			require.Equal(t, s.handleEnv(context.Background(), r), tc.expectedResult)
 			require.Equal(t, s.gitProtocolVersion, tc.expectedProtocolVersion)
 		})
 	}
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1632820920 -3600
#      Tue Sep 28 10:22:00 2021 +0100
# Node ID 962b8e7cb0931d684cf48da4ace0d0e81b0aaffa
# Parent  b582b99b4d02e9fc445bb6efe7cb760752a11e03
Add gitlab-sshd connection logging

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -29,21 +29,26 @@
 }
 
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+
 	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
 
 	for newChannel := range chans {
+		ctxlog.WithField("channel_type", newChannel.ChannelType).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
+			ctxlog.Info("connection: handle: unknown channel type")
 			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
 			continue
 		}
 		if !c.concurrentSessions.TryAcquire(1) {
+			ctxlog.Info("connection: handle: too many concurrent sessions")
 			newChannel.Reject(ssh.ResourceShortage, "too many concurrent sessions")
 			metrics.SshdHitMaxSessions.Inc()
 			continue
 		}
 		channel, requests, err := newChannel.Accept()
 		if err != nil {
-			log.WithError(err).Info("could not accept channel")
+			ctxlog.WithError(err).Error("connection: handle: accepting channel failed")
 			c.concurrentSessions.Release(1)
 			continue
 		}
@@ -54,11 +59,12 @@
 			// Prevent a panic in a single session from taking out the whole server
 			defer func() {
 				if err := recover(); err != nil {
-					log.WithContextFields(ctx, log.Fields{"recovered_error": err, "address": c.remoteAddr}).Warn("panic handling session")
+					ctxlog.WithField("recovered_error", err).Warn("panic handling session")
 				}
 			}()
 
 			handler(ctx, channel, requests)
+			ctxlog.Info("connection: handle: done")
 		}()
 	}
 }
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -149,19 +149,23 @@
 	defer s.wg.Done()
 	defer nconn.Close()
 
+	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
+	defer cancel()
+
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": remoteAddr})
+
 	// Prevent a panic in a single connection from taking out the whole server
 	defer func() {
 		if err := recover(); err != nil {
-			log.WithContextFields(ctx, log.Fields{"recovered_error": err, "address": remoteAddr}).Warn("panic handling session")
+			ctxlog.Warn("panic handling session")
 		}
 	}()
 
-	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
-	defer cancel()
+	ctxlog.Info("server: handleConn: start")
 
 	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
-		log.ContextLogger(ctx).WithError(err).Info("Failed to initialize SSH connection")
+		ctxlog.WithError(err).Error("server: handleConn: failed to initialize SSH connection")
 		return
 	}
 
@@ -178,4 +182,6 @@
 
 		session.handle(ctx, requests)
 	})
+
+	ctxlog.Info("server: handleConn: done")
 }
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1632835989 -3600
#      Tue Sep 28 14:33:09 2021 +0100
# Node ID 1b5d4bebb4468e9b6cc354e15266f3bed1cf9a78
# Parent  962b8e7cb0931d684cf48da4ace0d0e81b0aaffa
Add some initial logging guidelines

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -98,6 +98,21 @@
 
 Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
 
+## Logging Guidelines
+
+In general, it should be possible to determine the structure, but not content,
+of a gitlab-shell or gitlab-sshd session just from inspecting the logs. Some
+guidelines:
+
+- We use [`gitlab.com/gitlab-org/labkit/log`](https://pkg.go.dev/gitlab.com/gitlab-org/labkit/log)
+  for logging functionality
+- **Always** include a correlation ID
+- Log messages should be invariant and unique. Include accessory information in
+  fields, using `log.WithField`, `log.WithFields`, or `log.WithError`.
+- Log success cases as well as error cases
+- Logging too much is better than not logging enough. If a message seems too
+  verbose, consider reducing the log level before removing the message.
+
 ## Releasing
 
 See [PROCESS.md](./PROCESS.md)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1632856114 0
#      Tue Sep 28 19:08:34 2021 +0000
# Node ID 949d6ed84a338be26e427ac1b315bfb72beba093
# Parent  1638ae747fdece49dba9df3396ce3cbb2bd5d3e1
# Parent  1b5d4bebb4468e9b6cc354e15266f3bed1cf9a78
Merge branch '499-log-me-more' into 'main'

Add more logging to gitlab-sshd

See merge request gitlab-org/gitlab-shell!531

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -98,6 +98,21 @@
 
 Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
 
+## Logging Guidelines
+
+In general, it should be possible to determine the structure, but not content,
+of a gitlab-shell or gitlab-sshd session just from inspecting the logs. Some
+guidelines:
+
+- We use [`gitlab.com/gitlab-org/labkit/log`](https://pkg.go.dev/gitlab.com/gitlab-org/labkit/log)
+  for logging functionality
+- **Always** include a correlation ID
+- Log messages should be invariant and unique. Include accessory information in
+  fields, using `log.WithField`, `log.WithFields`, or `log.WithError`.
+- Log success cases as well as error cases
+- Logging too much is better than not logging enough. If a message seems too
+  verbose, consider reducing the log level before removing the message.
+
 ## Releasing
 
 See [PROCESS.md](./PROCESS.md)
diff --git a/cmd/gitlab-shell/command/command_test.go b/cmd/gitlab-shell/command/command_test.go
--- a/cmd/gitlab-shell/command/command_test.go
+++ b/cmd/gitlab-shell/command/command_test.go
@@ -267,7 +267,7 @@
 			executable:    &executable.Executable{Name: executable.GitlabShell},
 			env:           sshenv.Env{IsSSHConnection: true, OriginalCommand: `git receive-pack "`},
 			arguments:     []string{},
-			expectedError: "Invalid SSH command",
+			expectedError: "Invalid SSH command: invalid command line string",
 		},
 	}
 
diff --git a/internal/command/commandargs/shell.go b/internal/command/commandargs/shell.go
--- a/internal/command/commandargs/shell.go
+++ b/internal/command/commandargs/shell.go
@@ -1,7 +1,7 @@
 package commandargs
 
 import (
-	"errors"
+	"fmt"
 	"regexp"
 
 	"github.com/mattn/go-shellwords"
@@ -49,21 +49,16 @@
 
 func (s *Shell) validate() error {
 	if !s.Env.IsSSHConnection {
-		return errors.New("Only SSH allowed")
+		return fmt.Errorf("Only SSH allowed")
 	}
 
-	if !s.isValidSSHCommand() {
-		return errors.New("Invalid SSH command")
+	if err := s.ParseCommand(s.Env.OriginalCommand); err != nil {
+		return fmt.Errorf("Invalid SSH command: %w", err)
 	}
 
 	return nil
 }
 
-func (s *Shell) isValidSSHCommand() bool {
-	err := s.ParseCommand(s.Env.OriginalCommand)
-	return err == nil
-}
-
 func (s *Shell) parseWho() {
 	for _, argument := range s.Arguments {
 		if keyId := tryParseKeyId(argument); keyId != "" {
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -29,21 +29,26 @@
 }
 
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+
 	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
 
 	for newChannel := range chans {
+		ctxlog.WithField("channel_type", newChannel.ChannelType).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
+			ctxlog.Info("connection: handle: unknown channel type")
 			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
 			continue
 		}
 		if !c.concurrentSessions.TryAcquire(1) {
+			ctxlog.Info("connection: handle: too many concurrent sessions")
 			newChannel.Reject(ssh.ResourceShortage, "too many concurrent sessions")
 			metrics.SshdHitMaxSessions.Inc()
 			continue
 		}
 		channel, requests, err := newChannel.Accept()
 		if err != nil {
-			log.WithError(err).Info("could not accept channel")
+			ctxlog.WithError(err).Error("connection: handle: accepting channel failed")
 			c.concurrentSessions.Release(1)
 			continue
 		}
@@ -54,11 +59,12 @@
 			// Prevent a panic in a single session from taking out the whole server
 			defer func() {
 				if err := recover(); err != nil {
-					log.WithContextFields(ctx, log.Fields{"recovered_error": err, "address": c.remoteAddr}).Warn("panic handling session")
+					ctxlog.WithField("recovered_error", err).Warn("panic handling session")
 				}
 			}()
 
 			handler(ctx, channel, requests)
+			ctxlog.Info("connection: handle: done")
 		}()
 	}
 }
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -5,6 +5,7 @@
 	"errors"
 	"fmt"
 
+	"gitlab.com/gitlab-org/labkit/log"
 	"golang.org/x/crypto/ssh"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
@@ -40,16 +41,27 @@
 }
 
 func (s *session) handle(ctx context.Context, requests <-chan *ssh.Request) {
+	ctxlog := log.ContextLogger(ctx)
+
+	ctxlog.Debug("session: handle: entering request loop")
+
 	for req := range requests {
+		sessionLog := ctxlog.WithFields(log.Fields{
+			"bytesize":   len(req.Payload),
+			"type":       req.Type,
+			"want_reply": req.WantReply,
+		})
+		sessionLog.Debug("session: handle: request received")
+
 		var shouldContinue bool
 		switch req.Type {
 		case "env":
-			shouldContinue = s.handleEnv(req)
+			shouldContinue = s.handleEnv(ctx, req)
 		case "exec":
 			shouldContinue = s.handleExec(ctx, req)
 		case "shell":
 			shouldContinue = false
-			s.exit(s.handleShell(ctx, req))
+			s.exit(ctx, s.handleShell(ctx, req))
 		default:
 			// Ignore unknown requests but don't terminate the session
 			shouldContinue = true
@@ -58,18 +70,23 @@
 			}
 		}
 
+		sessionLog.WithField("should_continue", shouldContinue).Debug("session: handle: request processed")
+
 		if !shouldContinue {
 			s.channel.Close()
 			break
 		}
 	}
+
+	ctxlog.Debug("session: handle: exiting request loop")
 }
 
-func (s *session) handleEnv(req *ssh.Request) bool {
+func (s *session) handleEnv(ctx context.Context, req *ssh.Request) bool {
 	var accepted bool
 	var envRequest envRequest
 
 	if err := ssh.Unmarshal(req.Payload, &envRequest); err != nil {
+		log.ContextLogger(ctx).WithError(err).Error("session: handleEnv: failed to unmarshal request")
 		return false
 	}
 
@@ -85,6 +102,10 @@
 		req.Reply(accepted, []byte{})
 	}
 
+	log.WithContextFields(
+		ctx, log.Fields{"accepted": accepted, "env_request": envRequest},
+	).Debug("session: handleEnv: processed")
+
 	return true
 }
 
@@ -96,7 +117,8 @@
 
 	s.execCmd = execRequest.Command
 
-	s.exit(s.handleShell(ctx, req))
+	s.exit(ctx, s.handleShell(ctx, req))
+
 	return false
 }
 
@@ -119,28 +141,30 @@
 	}
 
 	cmd, err := shellCmd.NewWithKey(s.gitlabKeyId, env, s.cfg, rw)
-
 	if err != nil {
 		if !errors.Is(err, disallowedcommand.Error) {
-			s.toStderr("Failed to parse command: %v\n", err.Error())
+			s.toStderr(ctx, "Failed to parse command: %v\n", err.Error())
 		}
-		s.toStderr("Unknown command: %v\n", s.execCmd)
+		s.toStderr(ctx, "Unknown command: %v\n", s.execCmd)
 		return 128
 	}
 
 	if err := cmd.Execute(ctx); err != nil {
-		s.toStderr("remote: ERROR: %v\n", err.Error())
+		s.toStderr(ctx, "remote: ERROR: %v\n", err.Error())
 		return 1
 	}
 
 	return 0
 }
 
-func (s *session) toStderr(format string, args ...interface{}) {
-	fmt.Fprintf(s.channel.Stderr(), format, args...)
+func (s *session) toStderr(ctx context.Context, format string, args ...interface{}) {
+	out := fmt.Sprintf(format, args...)
+	log.WithContextFields(ctx, log.Fields{"stderr": out}).Debug("session: toStderr: output")
+	fmt.Fprint(s.channel.Stderr(), out)
 }
 
-func (s *session) exit(status uint32) {
+func (s *session) exit(ctx context.Context, status uint32) {
+	log.WithContextFields(ctx, log.Fields{"exit_status": status}).Info("session: exit: exiting")
 	req := exitStatusReq{ExitStatus: status}
 
 	s.channel.CloseWrite()
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -86,7 +86,7 @@
 			s := &session{gitProtocolVersion: "1"}
 			r := &ssh.Request{Payload: tc.payload}
 
-			require.Equal(t, s.handleEnv(r), tc.expectedResult)
+			require.Equal(t, s.handleEnv(context.Background(), r), tc.expectedResult)
 			require.Equal(t, s.gitProtocolVersion, tc.expectedProtocolVersion)
 		})
 	}
@@ -145,7 +145,7 @@
 		{
 			desc:             "fails to parse command",
 			cmd:              `\`,
-			errMsg:           "Failed to parse command: Invalid SSH command\nUnknown command: \\\n",
+			errMsg:           "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
 			gitlabKeyId:      "root",
 			expectedExitCode: 128,
 		}, {
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -149,19 +149,23 @@
 	defer s.wg.Done()
 	defer nconn.Close()
 
+	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
+	defer cancel()
+
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": remoteAddr})
+
 	// Prevent a panic in a single connection from taking out the whole server
 	defer func() {
 		if err := recover(); err != nil {
-			log.WithContextFields(ctx, log.Fields{"recovered_error": err, "address": remoteAddr}).Warn("panic handling session")
+			ctxlog.Warn("panic handling session")
 		}
 	}()
 
-	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
-	defer cancel()
+	ctxlog.Info("server: handleConn: start")
 
 	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
-		log.ContextLogger(ctx).WithError(err).Info("Failed to initialize SSH connection")
+		ctxlog.WithError(err).Error("server: handleConn: failed to initialize SSH connection")
 		return
 	}
 
@@ -178,4 +182,6 @@
 
 		session.handle(ctx, requests)
 	})
+
+	ctxlog.Info("server: handleConn: done")
 }
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1633000806 -3600
#      Thu Sep 30 12:20:06 2021 +0100
# Node ID 8ca91312fb2d11efe506f0ef78b866730ed85d9d
# Parent  949d6ed84a338be26e427ac1b315bfb72beba093
Resolve an error-swallowing issue

diff --git a/internal/command/lfsauthenticate/lfsauthenticate.go b/internal/command/lfsauthenticate/lfsauthenticate.go
--- a/internal/command/lfsauthenticate/lfsauthenticate.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate.go
@@ -6,6 +6,8 @@
 	"encoding/json"
 	"fmt"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
@@ -58,6 +60,11 @@
 	payload, err := c.authenticate(ctx, operation, repo, accessResponse.UserId)
 	if err != nil {
 		// return nothing just like Ruby's GitlabShell#lfs_authenticate does
+		log.WithContextFields(
+			ctx,
+			log.Fields{"operation": operation, "repo": repo, "user_id": accessResponse.UserId},
+		).WithError(err).Debug("lfsauthenticate: execute: LFS authentication failed")
+
 		return nil
 	}
 
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1633064762 0
#      Fri Oct 01 05:06:02 2021 +0000
# Node ID 323560847fcbbd43b825284c00ab0f6cc585cc88
# Parent  949d6ed84a338be26e427ac1b315bfb72beba093
# Parent  8ca91312fb2d11efe506f0ef78b866730ed85d9d
Merge branch '499-log-me-more-more' into 'main'

Resolve an error-swallowing issue

Closes #499

See merge request gitlab-org/gitlab-shell!533

diff --git a/internal/command/lfsauthenticate/lfsauthenticate.go b/internal/command/lfsauthenticate/lfsauthenticate.go
--- a/internal/command/lfsauthenticate/lfsauthenticate.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate.go
@@ -6,6 +6,8 @@
 	"encoding/json"
 	"fmt"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
@@ -58,6 +60,11 @@
 	payload, err := c.authenticate(ctx, operation, repo, accessResponse.UserId)
 	if err != nil {
 		// return nothing just like Ruby's GitlabShell#lfs_authenticate does
+		log.WithContextFields(
+			ctx,
+			log.Fields{"operation": operation, "repo": repo, "user_id": accessResponse.UserId},
+		).WithError(err).Debug("lfsauthenticate: execute: LFS authentication failed")
+
 		return nil
 	}
 
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1633521807 -3600
#      Wed Oct 06 13:03:27 2021 +0100
# Node ID c23605b604d1cb11e6a138a1cea3369338985559
# Parent  323560847fcbbd43b825284c00ab0f6cc585cc88
Fix logging channel type

Currently we get this in some log messages:

logrus_error="can not add field \"channel_type\""

This is because we're trying to add a function, rather than the result
of the function call (a string) to the `log.Fields`. Whoops!

Changelog: fixed

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -34,7 +34,7 @@
 	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
 
 	for newChannel := range chans {
-		ctxlog.WithField("channel_type", newChannel.ChannelType).Info("connection: handle: new channel requested")
+		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
 			ctxlog.Info("connection: handle: unknown channel type")
 			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1633528226 0
#      Wed Oct 06 13:50:26 2021 +0000
# Node ID 032c716977e806109188f1bd22b06ab814f5e34d
# Parent  323560847fcbbd43b825284c00ab0f6cc585cc88
# Parent  c23605b604d1cb11e6a138a1cea3369338985559
Merge branch 'fix-logging-channel-type' into 'main'

Fix logging channel type

See merge request gitlab-org/gitlab-shell!534

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -34,7 +34,7 @@
 	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
 
 	for newChannel := range chans {
-		ctxlog.WithField("channel_type", newChannel.ChannelType).Info("connection: handle: new channel requested")
+		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
 			ctxlog.Info("connection: handle: unknown channel type")
 			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1633599807 -3600
#      Thu Oct 07 10:43:27 2021 +0100
# Node ID ff79c56dbc2e1522347410ab416c27d188ec49f1
# Parent  032c716977e806109188f1bd22b06ab814f5e34d
Log command invocation

Use reflection to log the command we are about to execute, both in
gitlab-shell and gitlab-sshd. Include the environment, which has all
the context we need to understand what the command is expected to do.

Changelog: added

diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -3,6 +3,9 @@
 import (
 	"fmt"
 	"os"
+	"reflect"
+
+	"gitlab.com/gitlab-org/labkit/log"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
@@ -62,8 +65,15 @@
 	ctx, finished := command.Setup(executable.Name, config)
 	defer finished()
 
-	if err = cmd.Execute(ctx); err != nil {
+	cmdName := reflect.TypeOf(cmd).String()
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("gitlab-shell: main: executing command")
+
+	if err := cmd.Execute(ctx); err != nil {
+		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
 		console.DisplayWarningMessage(err.Error(), readWriter.ErrOut)
 		os.Exit(1)
 	}
+
+	ctxlog.Info("gitlab-shell: main: command executed successfully")
 }
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -4,6 +4,7 @@
 	"context"
 	"errors"
 	"fmt"
+	"reflect"
 
 	"gitlab.com/gitlab-org/labkit/log"
 	"golang.org/x/crypto/ssh"
@@ -149,11 +150,17 @@
 		return 128
 	}
 
+	cmdName := reflect.TypeOf(cmd).String()
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("session: handleShell: executing command")
+
 	if err := cmd.Execute(ctx); err != nil {
 		s.toStderr(ctx, "remote: ERROR: %v\n", err.Error())
 		return 1
 	}
 
+	ctxlog.Info("session: handleShell: command executed successfully")
+
 	return 0
 }
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1633601263 0
#      Thu Oct 07 10:07:43 2021 +0000
# Node ID 194f995a8cbb8e608b5a81d5ede35f9b9cf7ba0f
# Parent  032c716977e806109188f1bd22b06ab814f5e34d
# Parent  ff79c56dbc2e1522347410ab416c27d188ec49f1
Merge branch '499-log-command-invocation' into 'main'

Log command invocation

See merge request gitlab-org/gitlab-shell!535

diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -3,6 +3,9 @@
 import (
 	"fmt"
 	"os"
+	"reflect"
+
+	"gitlab.com/gitlab-org/labkit/log"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
@@ -62,8 +65,15 @@
 	ctx, finished := command.Setup(executable.Name, config)
 	defer finished()
 
-	if err = cmd.Execute(ctx); err != nil {
+	cmdName := reflect.TypeOf(cmd).String()
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("gitlab-shell: main: executing command")
+
+	if err := cmd.Execute(ctx); err != nil {
+		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
 		console.DisplayWarningMessage(err.Error(), readWriter.ErrOut)
 		os.Exit(1)
 	}
+
+	ctxlog.Info("gitlab-shell: main: command executed successfully")
 }
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -4,6 +4,7 @@
 	"context"
 	"errors"
 	"fmt"
+	"reflect"
 
 	"gitlab.com/gitlab-org/labkit/log"
 	"golang.org/x/crypto/ssh"
@@ -149,11 +150,17 @@
 		return 128
 	}
 
+	cmdName := reflect.TypeOf(cmd).String()
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("session: handleShell: executing command")
+
 	if err := cmd.Execute(ctx); err != nil {
 		s.toStderr(ctx, "remote: ERROR: %v\n", err.Error())
 		return 1
 	}
 
+	ctxlog.Info("session: handleShell: command executed successfully")
+
 	return 0
 }
 
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1631115800 0
#      Wed Sep 08 15:43:20 2021 +0000
# Node ID 849c7e7f1e78a3cd39fb3f0fd2eaa34feaf35b62
# Parent  9d6099095d58735db722bcca90bc4782f7614ba5
refactor: remove call to BuildNameToCertificate (deprecated)

diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -159,7 +159,6 @@
 			return nil, "", err
 		}
 		tlsConfig.Certificates = []tls.Certificate{cert}
-		tlsConfig.BuildNameToCertificate()
 	}
 
 	transport := &http.Transport{
diff --git a/client/testserver/testserver.go b/client/testserver/testserver.go
--- a/client/testserver/testserver.go
+++ b/client/testserver/testserver.go
@@ -73,7 +73,6 @@
 		Certificates: []tls.Certificate{cer},
 		MinVersion:   tls.VersionTLS12,
 	}
-	server.TLS.BuildNameToCertificate()
 
 	if clientCAPath != "" {
 		caCert, err := os.ReadFile(clientCAPath)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1634055177 0
#      Tue Oct 12 16:12:57 2021 +0000
# Node ID 037432d36801daacbfe185633c73a63d7a06690f
# Parent  194f995a8cbb8e608b5a81d5ede35f9b9cf7ba0f
# Parent  849c7e7f1e78a3cd39fb3f0fd2eaa34feaf35b62
Merge branch 'fix/name-certificate' into 'main'

refactor: remove call to BuildNameToCertificate (deprecated)

See merge request gitlab-org/gitlab-shell!515

diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -173,7 +173,6 @@
 			return nil, "", err
 		}
 		tlsConfig.Certificates = []tls.Certificate{cert}
-		tlsConfig.BuildNameToCertificate()
 	}
 
 	transport := &http.Transport{
diff --git a/client/testserver/testserver.go b/client/testserver/testserver.go
--- a/client/testserver/testserver.go
+++ b/client/testserver/testserver.go
@@ -73,7 +73,6 @@
 		Certificates: []tls.Certificate{cer},
 		MinVersion:   tls.VersionTLS12,
 	}
-	server.TLS.BuildNameToCertificate()
 
 	if clientCAPath != "" {
 		caCert, err := os.ReadFile(clientCAPath)
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1634122990 25200
#      Wed Oct 13 04:03:10 2021 -0700
# Node ID af06d28d33fd6dc15cf2022b6ccb4a468ce38e61
# Parent  037432d36801daacbfe185633c73a63d7a06690f
Update to Go v1.16.9

This is a security release: https://golang.org/doc/devel/release#go1.16

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.4
-go 1.16.8
+go 1.16.9
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1634126081 0
#      Wed Oct 13 11:54:41 2021 +0000
# Node ID 6f9d20402b23f29271d243d2e8c8764723b84611
# Parent  037432d36801daacbfe185633c73a63d7a06690f
# Parent  af06d28d33fd6dc15cf2022b6ccb4a468ce38e61
Merge branch 'sh-update-go-1.16.9' into 'main'

Update to Go v1.16.9

See merge request gitlab-org/gitlab-shell!537

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.4
-go 1.16.8
+go 1.16.9
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1634037154 -3600
#      Tue Oct 12 12:12:34 2021 +0100
# Node ID 53b3d6c9963071748b360a1e90081621150dca4c
# Parent  194f995a8cbb8e608b5a81d5ede35f9b9cf7ba0f
Reject non-proxied connections when proxy protocol is enabled

This will help to prevent misconfigurations.

Changelog: fixed

diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -94,6 +94,7 @@
 	if s.Config.Server.ProxyProtocol {
 		sshListener = &proxyproto.Listener{
 			Listener:          sshListener,
+			Policy:            unconditionalRequirePolicy,
 			ReadHeaderTimeout: ProxyHeaderTimeout,
 		}
 
@@ -185,3 +186,7 @@
 
 	ctxlog.Info("server: handleConn: done")
 }
+
+func unconditionalRequirePolicy(_ net.Addr) (proxyproto.Policy, error) {
+	return proxyproto.REQUIRE, nil
+}
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -47,6 +47,19 @@
 	verifyStatus(t, s, StatusClosed)
 }
 
+func TestListenAndServeRejectsPlainConnectionsWhenProxyProtocolEnabled(t *testing.T) {
+	s := setupServerWithProxyProtocolEnabled(t)
+	defer s.Shutdown()
+
+	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
+	if client != nil {
+		client.Close()
+	}
+
+	require.Error(t, err, "Expected plain SSH request to be failed")
+	require.Equal(t, err.Error(), "ssh: handshake failed: EOF")
+}
+
 func TestCorrelationId(t *testing.T) {
 	setupServer(t)
 
@@ -125,6 +138,18 @@
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
+	return setupServerWithConfig(t, nil)
+}
+
+func setupServerWithProxyProtocolEnabled(t *testing.T) *Server {
+	t.Helper()
+
+	return setupServerWithConfig(t, &config.Config{Server: config.ServerConfig{ProxyProtocol: true}})
+}
+
+func setupServerWithConfig(t *testing.T, cfg *config.Config) *Server {
+	t.Helper()
+
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/authorized_keys",
@@ -148,13 +173,20 @@
 	testhelper.PrepareTestRootDir(t)
 
 	url := testserver.StartSocketHttpServer(t, requests)
-	srvCfg := config.ServerConfig{
-		Listen:                  serverUrl,
-		ConcurrentSessionsLimit: 1,
-		HostKeyFiles:            []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")},
+
+	if cfg == nil {
+		cfg = &config.Config{}
 	}
 
-	s, err := NewServer(&config.Config{User: user, RootDir: "/tmp", GitlabUrl: url, Server: srvCfg})
+	// All things that don't need to be configurable in tests yet
+	cfg.GitlabUrl = url
+	cfg.RootDir = "/tmp"
+	cfg.User = user
+	cfg.Server.Listen = serverUrl
+	cfg.Server.ConcurrentSessionsLimit = 1
+	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
+
+	s, err := NewServer(cfg)
 	require.NoError(t, err)
 
 	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1634135548 0
#      Wed Oct 13 14:32:28 2021 +0000
# Node ID 3ed8918d8fadde500b017e7600cb8b65dc6b9409
# Parent  6f9d20402b23f29271d243d2e8c8764723b84611
# Parent  53b3d6c9963071748b360a1e90081621150dca4c
Merge branch '532-proxy-protocol-require' into 'main'

Reject non-proxied connections when proxy protocol is enabled

Closes #532

See merge request gitlab-org/gitlab-shell!536

diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -94,6 +94,7 @@
 	if s.Config.Server.ProxyProtocol {
 		sshListener = &proxyproto.Listener{
 			Listener:          sshListener,
+			Policy:            unconditionalRequirePolicy,
 			ReadHeaderTimeout: ProxyHeaderTimeout,
 		}
 
@@ -185,3 +186,7 @@
 
 	ctxlog.Info("server: handleConn: done")
 }
+
+func unconditionalRequirePolicy(_ net.Addr) (proxyproto.Policy, error) {
+	return proxyproto.REQUIRE, nil
+}
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -47,6 +47,19 @@
 	verifyStatus(t, s, StatusClosed)
 }
 
+func TestListenAndServeRejectsPlainConnectionsWhenProxyProtocolEnabled(t *testing.T) {
+	s := setupServerWithProxyProtocolEnabled(t)
+	defer s.Shutdown()
+
+	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
+	if client != nil {
+		client.Close()
+	}
+
+	require.Error(t, err, "Expected plain SSH request to be failed")
+	require.Equal(t, err.Error(), "ssh: handshake failed: EOF")
+}
+
 func TestCorrelationId(t *testing.T) {
 	setupServer(t)
 
@@ -125,6 +138,18 @@
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
+	return setupServerWithConfig(t, nil)
+}
+
+func setupServerWithProxyProtocolEnabled(t *testing.T) *Server {
+	t.Helper()
+
+	return setupServerWithConfig(t, &config.Config{Server: config.ServerConfig{ProxyProtocol: true}})
+}
+
+func setupServerWithConfig(t *testing.T, cfg *config.Config) *Server {
+	t.Helper()
+
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/authorized_keys",
@@ -148,13 +173,20 @@
 	testhelper.PrepareTestRootDir(t)
 
 	url := testserver.StartSocketHttpServer(t, requests)
-	srvCfg := config.ServerConfig{
-		Listen:                  serverUrl,
-		ConcurrentSessionsLimit: 1,
-		HostKeyFiles:            []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")},
+
+	if cfg == nil {
+		cfg = &config.Config{}
 	}
 
-	s, err := NewServer(&config.Config{User: user, RootDir: "/tmp", GitlabUrl: url, Server: srvCfg})
+	// All things that don't need to be configurable in tests yet
+	cfg.GitlabUrl = url
+	cfg.RootDir = "/tmp"
+	cfg.User = user
+	cfg.Server.Listen = serverUrl
+	cfg.Server.ConcurrentSessionsLimit = 1
+	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
+
+	s, err := NewServer(cfg)
 	require.NoError(t, err)
 
 	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1634134931 -3600
#      Wed Oct 13 15:22:11 2021 +0100
# Node ID 49b5bf76cf6457874952e00c5ff7ee9147142515
# Parent  6f9d20402b23f29271d243d2e8c8764723b84611
Improve logging for non-git commands

Several of our commands only touch the internal API, and go nowhere
near Gitaly. Improve logging for each of these in a single MR. In
general, we want to be able to tell what happened in the execution of
each command, and to track failures down to a specific line of code.

Changelog: added

diff --git a/internal/command/personalaccesstoken/personalaccesstoken.go b/internal/command/personalaccesstoken/personalaccesstoken.go
--- a/internal/command/personalaccesstoken/personalaccesstoken.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken.go
@@ -8,6 +8,8 @@
 	"strings"
 	"time"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -38,6 +40,10 @@
 		return err
 	}
 
+	log.WithContextFields(ctx, log.Fields{
+		"token_args": c.TokenArgs,
+	}).Info("personalaccesstoken: execute: requesting token")
+
 	response, err := c.getPersonalAccessToken(ctx)
 	if err != nil {
 		return err
diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -4,19 +4,17 @@
 	"bytes"
 	"context"
 	"errors"
-
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-
 	"io"
 	"net/http"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
+	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/pktline"
-
-	"gitlab.com/gitlab-org/labkit/log"
 )
 
 type Request struct {
@@ -59,12 +57,12 @@
 	request.Data.UserId = response.Who
 
 	for _, endpoint := range data.ApiEndpoints {
-		fields := log.Fields{
+		ctxlog := log.WithContextFields(ctx, log.Fields{
 			"primary_repo": data.PrimaryRepo,
 			"endpoint":     endpoint,
-		}
+		})
 
-		log.WithContextFields(ctx, fields).Info("Performing custom action")
+		ctxlog.Info("customaction: processApiEndpoints: Performing custom action")
 
 		response, err := c.performRequest(ctx, client, endpoint, request)
 		if err != nil {
@@ -90,6 +88,10 @@
 		} else {
 			output = c.readFromStdinNoEOF()
 		}
+		ctxlog.WithFields(log.Fields{
+			"eof_sent":    c.EOFSent,
+			"stdin_bytes": len(output),
+		}).Debug("customaction: processApiEndpoints: stdin buffered")
 
 		request.Output = output
 	}
diff --git a/internal/command/twofactorrecover/twofactorrecover.go b/internal/command/twofactorrecover/twofactorrecover.go
--- a/internal/command/twofactorrecover/twofactorrecover.go
+++ b/internal/command/twofactorrecover/twofactorrecover.go
@@ -6,6 +6,8 @@
 	"io"
 	"strings"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -21,9 +23,14 @@
 }
 
 func (c *Command) Execute(ctx context.Context) error {
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.Debug("twofactorrecover: execute: Waiting for user input")
+
 	if c.canContinue() {
+		ctxlog.Debug("twofactorrecover: execute: User chose to continue")
 		c.displayRecoveryCodes(ctx)
 	} else {
+		ctxlog.Debug("twofactorrecover: execute: User chose not to continue")
 		fmt.Fprintln(c.ReadWriter.Out, "\nNew recovery codes have *not* been generated. Existing codes will remain valid.")
 	}
 
@@ -43,9 +50,12 @@
 }
 
 func (c *Command) displayRecoveryCodes(ctx context.Context) {
+	ctxlog := log.ContextLogger(ctx)
+
 	codes, err := c.getRecoveryCodes(ctx)
 
 	if err == nil {
+		ctxlog.Debug("twofactorrecover: displayRecoveryCodes: recovery codes successfully generated")
 		messageWithCodes :=
 			"\nYour two-factor authentication recovery codes are:\n\n" +
 				strings.Join(codes, "\n") +
@@ -54,6 +64,7 @@
 				"a new device so you do not lose access to your account again.\n"
 		fmt.Fprint(c.ReadWriter.Out, messageWithCodes)
 	} else {
+		ctxlog.WithError(err).Error("twofactorrecover: displayRecoveryCodes: failed to generate recovery codes")
 		fmt.Fprintf(c.ReadWriter.Out, "\nAn error occurred while trying to generate new recovery codes.\n%v\n", err)
 	}
 }
diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -5,6 +5,8 @@
 	"fmt"
 	"io"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -18,11 +20,18 @@
 }
 
 func (c *Command) Execute(ctx context.Context) error {
-	err := c.verifyOTP(ctx, c.getOTP())
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.Info("twofactorverify: execute: waiting for user input")
+	otp := c.getOTP()
+
+	ctxlog.Info("twofactorverify: execute: verifying entered OTP")
+	err := c.verifyOTP(ctx, otp)
 	if err != nil {
+		ctxlog.WithError(err).Error("twofactorverify: execute: OTP verification failed")
 		return err
 	}
 
+	ctxlog.WithError(err).Info("twofactorverify: execute: OTP verified")
 	return nil
 }
 
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1634167422 0
#      Wed Oct 13 23:23:42 2021 +0000
# Node ID 3c1295f1e290c010533bfba9b81c8afd116c61b4
# Parent  3ed8918d8fadde500b017e7600cb8b65dc6b9409
# Parent  49b5bf76cf6457874952e00c5ff7ee9147142515
Merge branch '499-log-non-git-commands' into 'main'

Improve logging for non-git commands

See merge request gitlab-org/gitlab-shell!538

diff --git a/internal/command/personalaccesstoken/personalaccesstoken.go b/internal/command/personalaccesstoken/personalaccesstoken.go
--- a/internal/command/personalaccesstoken/personalaccesstoken.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken.go
@@ -8,6 +8,8 @@
 	"strings"
 	"time"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -38,6 +40,10 @@
 		return err
 	}
 
+	log.WithContextFields(ctx, log.Fields{
+		"token_args": c.TokenArgs,
+	}).Info("personalaccesstoken: execute: requesting token")
+
 	response, err := c.getPersonalAccessToken(ctx)
 	if err != nil {
 		return err
diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -4,19 +4,17 @@
 	"bytes"
 	"context"
 	"errors"
-
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-
 	"io"
 	"net/http"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
+	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/pktline"
-
-	"gitlab.com/gitlab-org/labkit/log"
 )
 
 type Request struct {
@@ -59,12 +57,12 @@
 	request.Data.UserId = response.Who
 
 	for _, endpoint := range data.ApiEndpoints {
-		fields := log.Fields{
+		ctxlog := log.WithContextFields(ctx, log.Fields{
 			"primary_repo": data.PrimaryRepo,
 			"endpoint":     endpoint,
-		}
+		})
 
-		log.WithContextFields(ctx, fields).Info("Performing custom action")
+		ctxlog.Info("customaction: processApiEndpoints: Performing custom action")
 
 		response, err := c.performRequest(ctx, client, endpoint, request)
 		if err != nil {
@@ -90,6 +88,10 @@
 		} else {
 			output = c.readFromStdinNoEOF()
 		}
+		ctxlog.WithFields(log.Fields{
+			"eof_sent":    c.EOFSent,
+			"stdin_bytes": len(output),
+		}).Debug("customaction: processApiEndpoints: stdin buffered")
 
 		request.Output = output
 	}
diff --git a/internal/command/twofactorrecover/twofactorrecover.go b/internal/command/twofactorrecover/twofactorrecover.go
--- a/internal/command/twofactorrecover/twofactorrecover.go
+++ b/internal/command/twofactorrecover/twofactorrecover.go
@@ -6,6 +6,8 @@
 	"io"
 	"strings"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -21,9 +23,14 @@
 }
 
 func (c *Command) Execute(ctx context.Context) error {
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.Debug("twofactorrecover: execute: Waiting for user input")
+
 	if c.canContinue() {
+		ctxlog.Debug("twofactorrecover: execute: User chose to continue")
 		c.displayRecoveryCodes(ctx)
 	} else {
+		ctxlog.Debug("twofactorrecover: execute: User chose not to continue")
 		fmt.Fprintln(c.ReadWriter.Out, "\nNew recovery codes have *not* been generated. Existing codes will remain valid.")
 	}
 
@@ -43,9 +50,12 @@
 }
 
 func (c *Command) displayRecoveryCodes(ctx context.Context) {
+	ctxlog := log.ContextLogger(ctx)
+
 	codes, err := c.getRecoveryCodes(ctx)
 
 	if err == nil {
+		ctxlog.Debug("twofactorrecover: displayRecoveryCodes: recovery codes successfully generated")
 		messageWithCodes :=
 			"\nYour two-factor authentication recovery codes are:\n\n" +
 				strings.Join(codes, "\n") +
@@ -54,6 +64,7 @@
 				"a new device so you do not lose access to your account again.\n"
 		fmt.Fprint(c.ReadWriter.Out, messageWithCodes)
 	} else {
+		ctxlog.WithError(err).Error("twofactorrecover: displayRecoveryCodes: failed to generate recovery codes")
 		fmt.Fprintf(c.ReadWriter.Out, "\nAn error occurred while trying to generate new recovery codes.\n%v\n", err)
 	}
 }
diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -5,6 +5,8 @@
 	"fmt"
 	"io"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -18,11 +20,18 @@
 }
 
 func (c *Command) Execute(ctx context.Context) error {
-	err := c.verifyOTP(ctx, c.getOTP())
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.Info("twofactorverify: execute: waiting for user input")
+	otp := c.getOTP()
+
+	ctxlog.Info("twofactorverify: execute: verifying entered OTP")
+	err := c.verifyOTP(ctx, otp)
 	if err != nil {
+		ctxlog.WithError(err).Error("twofactorverify: execute: OTP verification failed")
 		return err
 	}
 
+	ctxlog.WithError(err).Info("twofactorverify: execute: OTP verified")
 	return nil
 }
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1634718828 -10800
#      Wed Oct 20 11:33:48 2021 +0300
# Node ID 00972efe4bd04602f1243a56c5bfbffb6c7ed184
# Parent  3c1295f1e290c010533bfba9b81c8afd116c61b4
Log SSL_CERT_DIR when it's configured

diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -14,6 +14,8 @@
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
@@ -93,6 +95,8 @@
 
 func (c *Config) ApplyGlobalState() {
 	if c.SslCertDir != "" {
+		log.WithFields(log.Fields{"ssl_cert_dir": c.SslCertDir}).Info("SSL_CERT_DIR is configured")
+
 		os.Setenv("SSL_CERT_DIR", c.SslCertDir)
 	}
 }
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1634718850 -10800
#      Wed Oct 20 11:34:10 2021 +0300
# Node ID 67bcabd5a37384dd196ace1d594d7b2e97e10c51
# Parent  00972efe4bd04602f1243a56c5bfbffb6c7ed184
Add logging to RunGitalyCommand func

diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -45,17 +45,24 @@
 func (gc *GitalyCommand) RunGitalyCommand(ctx context.Context, handler GitalyHandlerFunc) error {
 	conn, err := getConn(ctx, gc)
 	if err != nil {
+		log.ContextLogger(ctx).WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Failed to get connection to execute Git command")
+
 		return err
 	}
 	defer conn.Close()
 
 	childCtx := withOutgoingMetadata(ctx, gc.Features)
-	_, err = handler(childCtx, conn)
+	ctxlog := log.ContextLogger(childCtx)
+	exitStatus, err := handler(childCtx, conn)
 
-	if err != nil && grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
-		log.WithError(err).Error("Gitaly is unavailable")
+	if err != nil {
+		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
+			ctxlog.WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Gitaly is unavailable")
 
-		return fmt.Errorf("Git service is temporarily unavailable")
+			return fmt.Errorf("The git server, Gitaly, is not available at this time. Please contact your administrator.")
+		}
+
+		ctxlog.WithError(err).WithFields(log.Fields{"exit_status": exitStatus}).Error("Failed to execute Git command")
 	}
 
 	return err
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -56,7 +56,7 @@
 	expectedErr := grpcstatus.Error(grpccodes.Unavailable, "error")
 	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, expectedErr))
 
-	require.EqualError(t, err, "Git service is temporarily unavailable")
+	require.EqualError(t, err, "The git server, Gitaly, is not available at this time. Please contact your administrator.")
 }
 
 func TestRunGitalyCommandMetadata(t *testing.T) {
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1634788211 0
#      Thu Oct 21 03:50:11 2021 +0000
# Node ID aebee2f15bfbebbbf155b4ac968885deacf868fd
# Parent  3c1295f1e290c010533bfba9b81c8afd116c61b4
# Parent  67bcabd5a37384dd196ace1d594d7b2e97e10c51
Merge branch 'id-logging-for-handler' into 'main'

Add logging to handler/exec.go and config/config.go

See merge request gitlab-org/gitlab-shell!539

diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -14,6 +14,8 @@
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+
+	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
@@ -93,6 +95,8 @@
 
 func (c *Config) ApplyGlobalState() {
 	if c.SslCertDir != "" {
+		log.WithFields(log.Fields{"ssl_cert_dir": c.SslCertDir}).Info("SSL_CERT_DIR is configured")
+
 		os.Setenv("SSL_CERT_DIR", c.SslCertDir)
 	}
 }
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -45,17 +45,24 @@
 func (gc *GitalyCommand) RunGitalyCommand(ctx context.Context, handler GitalyHandlerFunc) error {
 	conn, err := getConn(ctx, gc)
 	if err != nil {
+		log.ContextLogger(ctx).WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Failed to get connection to execute Git command")
+
 		return err
 	}
 	defer conn.Close()
 
 	childCtx := withOutgoingMetadata(ctx, gc.Features)
-	_, err = handler(childCtx, conn)
+	ctxlog := log.ContextLogger(childCtx)
+	exitStatus, err := handler(childCtx, conn)
 
-	if err != nil && grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
-		log.WithError(err).Error("Gitaly is unavailable")
+	if err != nil {
+		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
+			ctxlog.WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Gitaly is unavailable")
 
-		return fmt.Errorf("Git service is temporarily unavailable")
+			return fmt.Errorf("The git server, Gitaly, is not available at this time. Please contact your administrator.")
+		}
+
+		ctxlog.WithError(err).WithFields(log.Fields{"exit_status": exitStatus}).Error("Failed to execute Git command")
 	}
 
 	return err
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -56,7 +56,7 @@
 	expectedErr := grpcstatus.Error(grpccodes.Unavailable, "error")
 	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, expectedErr))
 
-	require.EqualError(t, err, "Git service is temporarily unavailable")
+	require.EqualError(t, err, "The git server, Gitaly, is not available at this time. Please contact your administrator.")
 }
 
 func TestRunGitalyCommandMetadata(t *testing.T) {
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1636576318 28800
#      Wed Nov 10 12:31:58 2021 -0800
# Node ID 145796e7e2955661ba04f0e5bbd3862dcad84140
# Parent  aebee2f15bfbebbbf155b4ac968885deacf868fd
Relax key and username matching for sshd

Due to the way sshd works, gitlab-shell could be called with a single
string in the form:

```
/path/to/gitlab-shell -c key-id
```

However, due to the tightening of the regular expressions in fcff692b
this string no longer matches, so logins would fail with:

```
Failed to get username: who='' is invalid
```

This can be reproduced by changing the user's shell to point to
gitlab-shell. For example:

```
usermod git -s /opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell
```

While setting gitlab-shell as the user's shell isn't officially
supported, gitlab-shell still should be able to cope with the key being
specified as the last argument. We now split the argument list and use
the last value.

Relates to https://gitlab.com/gitlab-org/gitlab-shell/-/issues/530

diff --git a/cmd/gitlab-shell/command/command_test.go b/cmd/gitlab-shell/command/command_test.go
--- a/cmd/gitlab-shell/command/command_test.go
+++ b/cmd/gitlab-shell/command/command_test.go
@@ -170,6 +170,27 @@
 			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "username-key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
 		},
 		{
+			desc:         "It finds the key id if the key is listed as the last argument",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "gitlab-shell -c key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "gitlab-shell -c key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the username if the username is listed as the last argument",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "gitlab-shell -c username-jane-doe"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "gitlab-shell -c username-jane-doe"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "jane-doe", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the key id only if the last argument is of <key-id> format",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "gitlab-shell -c username-key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "gitlab-shell -c username-key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
 			desc:         "It finds the username in any passed arguments",
 			executable:   &executable.Executable{Name: executable.GitlabShell},
 			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
diff --git a/internal/command/commandargs/shell.go b/internal/command/commandargs/shell.go
--- a/internal/command/commandargs/shell.go
+++ b/internal/command/commandargs/shell.go
@@ -3,6 +3,7 @@
 import (
 	"fmt"
 	"regexp"
+	"strings"
 
 	"github.com/mattn/go-shellwords"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
@@ -73,26 +74,29 @@
 	}
 }
 
-func tryParseKeyId(argument string) string {
-	matchInfo := whoKeyRegex.FindStringSubmatch(argument)
+func tryParse(r *regexp.Regexp, argument string) string {
+	// sshd may execute the session for AuthorizedKeysCommand in multiple ways:
+	// 1. key-id
+	// 2. /path/to/shell -c key-id
+	args := strings.Split(argument, " ")
+	lastArg := args[len(args)-1]
+
+	matchInfo := r.FindStringSubmatch(lastArg)
 	if len(matchInfo) == 2 {
 		// The first element is the full matched string
-		// The second element is the named `keyid`
+		// The second element is the named `keyid` or `username`
 		return matchInfo[1]
 	}
 
 	return ""
 }
 
+func tryParseKeyId(argument string) string {
+	return tryParse(whoKeyRegex, argument)
+}
+
 func tryParseUsername(argument string) string {
-	matchInfo := whoUsernameRegex.FindStringSubmatch(argument)
-	if len(matchInfo) == 2 {
-		// The first element is the full matched string
-		// The second element is the named `username`
-		return matchInfo[1]
-	}
-
-	return ""
+	return tryParse(whoUsernameRegex, argument)
 }
 
 func (s *Shell) ParseCommand(commandString string) error {
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1636682976 0
#      Fri Nov 12 02:09:36 2021 +0000
# Node ID 5396b70d69efbd3f81c5ab5e2ff707ccf37891e9
# Parent  aebee2f15bfbebbbf155b4ac968885deacf868fd
# Parent  145796e7e2955661ba04f0e5bbd3862dcad84140
Merge branch 'sh-improve-key-matching-sshd' into 'main'

Relax key and username matching for sshd

See merge request gitlab-org/gitlab-shell!540

diff --git a/cmd/gitlab-shell/command/command_test.go b/cmd/gitlab-shell/command/command_test.go
--- a/cmd/gitlab-shell/command/command_test.go
+++ b/cmd/gitlab-shell/command/command_test.go
@@ -170,6 +170,27 @@
 			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "username-key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
 		},
 		{
+			desc:         "It finds the key id if the key is listed as the last argument",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "gitlab-shell -c key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "gitlab-shell -c key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the username if the username is listed as the last argument",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "gitlab-shell -c username-jane-doe"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "gitlab-shell -c username-jane-doe"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "jane-doe", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the key id only if the last argument is of <key-id> format",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "gitlab-shell -c username-key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "gitlab-shell -c username-key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
 			desc:         "It finds the username in any passed arguments",
 			executable:   &executable.Executable{Name: executable.GitlabShell},
 			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
diff --git a/internal/command/commandargs/shell.go b/internal/command/commandargs/shell.go
--- a/internal/command/commandargs/shell.go
+++ b/internal/command/commandargs/shell.go
@@ -3,6 +3,7 @@
 import (
 	"fmt"
 	"regexp"
+	"strings"
 
 	"github.com/mattn/go-shellwords"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
@@ -73,26 +74,29 @@
 	}
 }
 
-func tryParseKeyId(argument string) string {
-	matchInfo := whoKeyRegex.FindStringSubmatch(argument)
+func tryParse(r *regexp.Regexp, argument string) string {
+	// sshd may execute the session for AuthorizedKeysCommand in multiple ways:
+	// 1. key-id
+	// 2. /path/to/shell -c key-id
+	args := strings.Split(argument, " ")
+	lastArg := args[len(args)-1]
+
+	matchInfo := r.FindStringSubmatch(lastArg)
 	if len(matchInfo) == 2 {
 		// The first element is the full matched string
-		// The second element is the named `keyid`
+		// The second element is the named `keyid` or `username`
 		return matchInfo[1]
 	}
 
 	return ""
 }
 
+func tryParseKeyId(argument string) string {
+	return tryParse(whoKeyRegex, argument)
+}
+
 func tryParseUsername(argument string) string {
-	matchInfo := whoUsernameRegex.FindStringSubmatch(argument)
-	if len(matchInfo) == 2 {
-		// The first element is the full matched string
-		// The second element is the named `username`
-		return matchInfo[1]
-	}
-
-	return ""
+	return tryParse(whoUsernameRegex, argument)
 }
 
 func (s *Shell) ParseCommand(commandString string) error {
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1636695628 -39600
#      Fri Nov 12 16:40:28 2021 +1100
# Node ID 9ae43afdd2f07aafbbd4fdcd94d4a9abe54ce4e8
# Parent  5396b70d69efbd3f81c5ab5e2ff707ccf37891e9
Fix go -> golang in .tool-versions

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.4
-go 1.16.9
+golang 1.16.9
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1636695645 -39600
#      Fri Nov 12 16:40:45 2021 +1100
# Node ID ba313aed46dd6720f7c4a3af2b4219b8f51db8c3
# Parent  9ae43afdd2f07aafbbd4fdcd94d4a9abe54ce4e8
Bump golang in .tool-versions to 1.16.10

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.4
-golang 1.16.9
+golang 1.16.10
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1636946261 0
#      Mon Nov 15 03:17:41 2021 +0000
# Node ID c9169d5139f0ecbc0c27419cb87f320ebde12d65
# Parent  5396b70d69efbd3f81c5ab5e2ff707ccf37891e9
# Parent  ba313aed46dd6720f7c4a3af2b4219b8f51db8c3
Merge branch 'ashmckenzie/fix-go-definition-in-tool-versions' into 'main'

Fix golang definition in tool versions

See merge request gitlab-org/gitlab-shell!542

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.4
-go 1.16.9
+golang 1.16.10
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1636952256 -39600
#      Mon Nov 15 15:57:36 2021 +1100
# Node ID a820a6cf69e0096d959393848619dbcd24312ed1
# Parent  c9169d5139f0ecbc0c27419cb87f320ebde12d65
Add missing v13.21.0 changelog entries

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,13 @@
 v13.21.0
 
+- Switch to labkit for logging system setup !504
+- Remove some unreliable tests !503
+- Make gofmt check fail if there are any matching files !500
+- Update go-proxyproto to v0.6.0 !499
+- Switch to labkit/log for logging functionality !498
+- Unit tests for internal/sshd/connection.go !497
+- Prometheus metrics for HTTP requests !496
+- Refactor testhelper.PrepareTestRootDir using t.Cleanup !49
 - Change default logging format to JSON !476
 - Shutdown sshd gracefully !484
 - Provide liveness and readiness probes !494
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1636952283 -39600
#      Mon Nov 15 15:58:03 2021 +1100
# Node ID 5a7116d7fe463c64d73cea1ca9fe75b5427eaa5e
# Parent  a820a6cf69e0096d959393848619dbcd24312ed1
Add missing v13.21.1 changelog entry

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v13.21.1
+
+- Only validate SSL cert file exists if a value is supplied !527
+
 v13.21.0
 
 - Switch to labkit for logging system setup !504
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1636952314 -39600
#      Mon Nov 15 15:58:34 2021 +1100
# Node ID 1fa4f69b6d2c4a63eb332d3964c03832aa63cb2a
# Parent  5a7116d7fe463c64d73cea1ca9fe75b5427eaa5e
Release v13.22.0

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,22 @@
+v13.22.0
+
+- Relax key and username matching for sshd !540
+- Add logging to handler/exec.go and config/config.go !539
+- Improve logging for non-git commands !538
+- Update to Go v1.16.9 !537
+- Reject non-proxied connections when proxy protocol is enabled !536
+- Log command invocation !535
+- Fix logging channel type !534
+- Resolve an error-swallowing issue !533
+- Add more logging to gitlab-sshd !531
+- Respect log-level configuration again !530
+- Improve err message given when Gitaly unavailable !526
+- makefile: properly escape '$' in VERSION_STRING !525
+- Add context fields to logging !524
+- Extract server config related code out of sshd.go !523
+- Add TestInvalidClientConfig and TestNewServerWithoutHosts for sshd.go !518
+- Update Ruby version to 2.7.4 and add Go version 1.16.8 for tooling !517
+
 v13.21.1
 
 - Only validate SSL cert file exists if a value is supplied !527
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1636952729 0
#      Mon Nov 15 05:05:29 2021 +0000
# Node ID 7adeb3e289bd067e243d5b42fa75461800372f58
# Parent  1fa4f69b6d2c4a63eb332d3964c03832aa63cb2a
Fix merge request IID on changelog

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -30,7 +30,7 @@
 - Switch to labkit/log for logging functionality !498
 - Unit tests for internal/sshd/connection.go !497
 - Prometheus metrics for HTTP requests !496
-- Refactor testhelper.PrepareTestRootDir using t.Cleanup !49
+- Refactor testhelper.PrepareTestRootDir using t.Cleanup !493
 - Change default logging format to JSON !476
 - Shutdown sshd gracefully !484
 - Provide liveness and readiness probes !494
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1636954267 0
#      Mon Nov 15 05:31:07 2021 +0000
# Node ID 7242f61c8d02f80f1a48936aa283a82526327a8c
# Parent  c9169d5139f0ecbc0c27419cb87f320ebde12d65
# Parent  7adeb3e289bd067e243d5b42fa75461800372f58
Merge branch 'ashmckenzie/13-22-release' into 'main'

Release v13.22.0

See merge request gitlab-org/gitlab-shell!541

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,36 @@
+v13.22.0
+
+- Relax key and username matching for sshd !540
+- Add logging to handler/exec.go and config/config.go !539
+- Improve logging for non-git commands !538
+- Update to Go v1.16.9 !537
+- Reject non-proxied connections when proxy protocol is enabled !536
+- Log command invocation !535
+- Fix logging channel type !534
+- Resolve an error-swallowing issue !533
+- Add more logging to gitlab-sshd !531
+- Respect log-level configuration again !530
+- Improve err message given when Gitaly unavailable !526
+- makefile: properly escape '$' in VERSION_STRING !525
+- Add context fields to logging !524
+- Extract server config related code out of sshd.go !523
+- Add TestInvalidClientConfig and TestNewServerWithoutHosts for sshd.go !518
+- Update Ruby version to 2.7.4 and add Go version 1.16.8 for tooling !517
+
+v13.21.1
+
+- Only validate SSL cert file exists if a value is supplied !527
+
 v13.21.0
 
+- Switch to labkit for logging system setup !504
+- Remove some unreliable tests !503
+- Make gofmt check fail if there are any matching files !500
+- Update go-proxyproto to v0.6.0 !499
+- Switch to labkit/log for logging functionality !498
+- Unit tests for internal/sshd/connection.go !497
+- Prometheus metrics for HTTP requests !496
+- Refactor testhelper.PrepareTestRootDir using t.Cleanup !493
 - Change default logging format to JSON !476
 - Shutdown sshd gracefully !484
 - Provide liveness and readiness probes !494
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1636954556 -28800
#      Mon Nov 15 13:35:56 2021 +0800
# Node ID e6074be751b66fca60b74d28efaccf0474468b50
# Parent  7242f61c8d02f80f1a48936aa283a82526327a8c
Update gitlab-shell VERSION to 13.22.0

diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.21.0
+13.22.0
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1636956284 0
#      Mon Nov 15 06:04:44 2021 +0000
# Node ID cf60d9b274666d35bdd31165afdcbe33b162fc4f
# Parent  7242f61c8d02f80f1a48936aa283a82526327a8c
# Parent  e6074be751b66fca60b74d28efaccf0474468b50
Merge branch 'pb-update-version-file-13-22-0' into 'main'

Update gitlab-shell VERSION to 13.22.0

See merge request gitlab-org/gitlab-shell!543

diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.21.0
+13.22.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1636989303 -10800
#      Mon Nov 15 18:15:03 2021 +0300
# Node ID b3ec3749ea59eed941f59f426a35371ff366148a
# Parent  cf60d9b274666d35bdd31165afdcbe33b162fc4f
Refactor flaky test case in sshd_test

- Use require.Regexp to expect ssh handshake error
- Use require.Eventually to refactor verifyStatus

diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -48,8 +48,7 @@
 }
 
 func TestListenAndServeRejectsPlainConnectionsWhenProxyProtocolEnabled(t *testing.T) {
-	s := setupServerWithProxyProtocolEnabled(t)
-	defer s.Shutdown()
+	setupServerWithProxyProtocolEnabled(t)
 
 	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
 	if client != nil {
@@ -57,7 +56,7 @@
 	}
 
 	require.Error(t, err, "Expected plain SSH request to be failed")
-	require.Equal(t, err.Error(), "ssh: handshake failed: EOF")
+	require.Regexp(t, "ssh: handshake failed", err.Error())
 }
 
 func TestCorrelationId(t *testing.T) {
@@ -227,14 +226,5 @@
 }
 
 func verifyStatus(t *testing.T, s *Server, st status) {
-	for i := 5; i < 500; i += 50 {
-		if s.getStatus() == st {
-			break
-		}
-
-		// Sleep incrementally ~2s in total
-		time.Sleep(time.Duration(i) * time.Millisecond)
-	}
-
-	require.Equal(t, st, s.getStatus())
+	require.Eventually(t, func() bool { return s.getStatus() == st }, 2*time.Second, time.Millisecond)
 }
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1637031908 0
#      Tue Nov 16 03:05:08 2021 +0000
# Node ID aacf29b3a02a6ac9b18470f1a761232243816c1d
# Parent  cf60d9b274666d35bdd31165afdcbe33b162fc4f
# Parent  b3ec3749ea59eed941f59f426a35371ff366148a
Merge branch 'id-fix-flaky-test' into 'main'

Refactor flaky test case in sshd_test

See merge request gitlab-org/gitlab-shell!545

diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -48,8 +48,7 @@
 }
 
 func TestListenAndServeRejectsPlainConnectionsWhenProxyProtocolEnabled(t *testing.T) {
-	s := setupServerWithProxyProtocolEnabled(t)
-	defer s.Shutdown()
+	setupServerWithProxyProtocolEnabled(t)
 
 	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
 	if client != nil {
@@ -57,7 +56,7 @@
 	}
 
 	require.Error(t, err, "Expected plain SSH request to be failed")
-	require.Equal(t, err.Error(), "ssh: handshake failed: EOF")
+	require.Regexp(t, "ssh: handshake failed", err.Error())
 }
 
 func TestCorrelationId(t *testing.T) {
@@ -227,14 +226,5 @@
 }
 
 func verifyStatus(t *testing.T, s *Server, st status) {
-	for i := 5; i < 500; i += 50 {
-		if s.getStatus() == st {
-			break
-		}
-
-		// Sleep incrementally ~2s in total
-		time.Sleep(time.Duration(i) * time.Millisecond)
-	}
-
-	require.Equal(t, st, s.getStatus())
+	require.Eventually(t, func() bool { return s.getStatus() == st }, 2*time.Second, time.Millisecond)
 }
# HG changeset patch
# User Patrick Steinhardt <psteinhardt@gitlab.com>
# Date 1636959561 -3600
#      Mon Nov 15 07:59:21 2021 +0100
# Node ID 6e4e76df7ef9d3fbc346c1eebedee19babe99e2f
# Parent  cf60d9b274666d35bdd31165afdcbe33b162fc4f
Fix usage of out-of-date Gitaly images

Our CI jobs and docker-compose pull in the "latest" tag of Gitaly. As it
turns out though, "latest" is pointing to Gitaly v13.3.0-rc5, which is
definitely not the latest versionat this point in time. This is because
CNG was converted to not use the "latest" tag anymore, but instead to
use a tag called "master" in gitlab-org/build/CNG!519.

Fix this by using the new "master" tag instead.

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -45,7 +45,7 @@
     - go version
     - which go
   services:
-    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:latest
+    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:master
       # Disable the hooks so we don't have to stub the GitLab API
       command: ["bash", "-c", "mkdir -p /home/git/repositories && rm -rf /srv/gitlab-shell/hooks/* && exec /usr/bin/env GITALY_TESTING_NO_GIT_HOOKS=1 /scripts/process-wrapper"]
       alias: gitaly
diff --git a/docker-compose.yml b/docker-compose.yml
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -3,6 +3,6 @@
   gitaly:
     environment:
       - GITALY_TESTING_NO_GIT_HOOKS=1
-    image: registry.gitlab.com/gitlab-org/build/cng/gitaly:latest
+    image: registry.gitlab.com/gitlab-org/build/cng/gitaly:master
     ports:
       - '8075:8075'
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1637033218 0
#      Tue Nov 16 03:26:58 2021 +0000
# Node ID 268af504e4c51836701b070a5ecf81d2ca12ca63
# Parent  aacf29b3a02a6ac9b18470f1a761232243816c1d
# Parent  6e4e76df7ef9d3fbc346c1eebedee19babe99e2f
Merge branch 'pks-gitaly-cng-latest' into 'main'

Fix usage of out-of-date Gitaly images

See merge request gitlab-org/gitlab-shell!544

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -45,7 +45,7 @@
     - go version
     - which go
   services:
-    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:latest
+    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:master
       # Disable the hooks so we don't have to stub the GitLab API
       command: ["bash", "-c", "mkdir -p /home/git/repositories && rm -rf /srv/gitlab-shell/hooks/* && exec /usr/bin/env GITALY_TESTING_NO_GIT_HOOKS=1 /scripts/process-wrapper"]
       alias: gitaly
diff --git a/docker-compose.yml b/docker-compose.yml
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -3,6 +3,6 @@
   gitaly:
     environment:
       - GITALY_TESTING_NO_GIT_HOOKS=1
-    image: registry.gitlab.com/gitlab-org/build/cng/gitaly:latest
+    image: registry.gitlab.com/gitlab-org/build/cng/gitaly:master
     ports:
       - '8075:8075'
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1637672297 -10800
#      Tue Nov 23 15:58:17 2021 +0300
# Node ID 2970ae7c21dd298a7ccadad021abbf082b56ccdc
# Parent  268af504e4c51836701b070a5ecf81d2ca12ca63
Remove SSL_CERT_DIR logging

This log entry doesn't respect log level, because the log level
is configured after this logging happens

diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -14,8 +14,6 @@
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
-
-	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
@@ -95,8 +93,6 @@
 
 func (c *Config) ApplyGlobalState() {
 	if c.SslCertDir != "" {
-		log.WithFields(log.Fields{"ssl_cert_dir": c.SslCertDir}).Info("SSL_CERT_DIR is configured")
-
 		os.Setenv("SSL_CERT_DIR", c.SslCertDir)
 	}
 }
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1637732912 0
#      Wed Nov 24 05:48:32 2021 +0000
# Node ID f593018f1e6261b7a130f800ff583494c97ce822
# Parent  268af504e4c51836701b070a5ecf81d2ca12ca63
# Parent  2970ae7c21dd298a7ccadad021abbf082b56ccdc
Merge branch 'id-remove-ssl-cert-dir-logging' into 'main'

Remove SSL_CERT_DIR logging

See merge request gitlab-org/gitlab-shell!546

diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -14,8 +14,6 @@
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
-
-	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
@@ -95,8 +93,6 @@
 
 func (c *Config) ApplyGlobalState() {
 	if c.SslCertDir != "" {
-		log.WithFields(log.Fields{"ssl_cert_dir": c.SslCertDir}).Info("SSL_CERT_DIR is configured")
-
 		os.Setenv("SSL_CERT_DIR", c.SslCertDir)
 	}
 }
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1637742836 -10800
#      Wed Nov 24 11:33:56 2021 +0300
# Node ID fc0405af6d59acb6168e61f8859a2207fdc8609f
# Parent  f593018f1e6261b7a130f800ff583494c97ce822
Release v13.22.1

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v13.22.1
+
+- Remove SSL_CERT_DIR logging !546
+
 v13.22.0
 
 - Relax key and username matching for sshd !540
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.22.0
+13.22.1
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1637743366 0
#      Wed Nov 24 08:42:46 2021 +0000
# Node ID 20c9325c66f15b775f94cd0b11bb6df45d9510db
# Parent  f593018f1e6261b7a130f800ff583494c97ce822
# Parent  fc0405af6d59acb6168e61f8859a2207fdc8609f
Merge branch 'id-release-13-22-1' into 'main'

Release v13.22.1

See merge request gitlab-org/gitlab-shell!547

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v13.22.1
+
+- Remove SSL_CERT_DIR logging !546
+
 v13.22.0
 
 - Relax key and username matching for sshd !540
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.22.0
+13.22.1
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1639082342 28800
#      Thu Dec 09 12:39:02 2021 -0800
# Node ID 2e24492b1690fd146b2031660a60ee7d8c71f697
# Parent  20c9325c66f15b775f94cd0b11bb6df45d9510db
Bump .tool_versions to use Go v1.16.12

Part of https://gitlab.com/groups/gitlab-org/-/epics/7111

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.4
-golang 1.16.10
+golang 1.16.12
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1639096011 0
#      Fri Dec 10 00:26:51 2021 +0000
# Node ID 0933769f90885c8c0ef6db40d1aeeea758a7632d
# Parent  20c9325c66f15b775f94cd0b11bb6df45d9510db
# Parent  2e24492b1690fd146b2031660a60ee7d8c71f697
Merge branch 'sh-bump-go-1.16.12' into 'main'

Bump .tool_versions to use Go v1.16.12

See merge request gitlab-org/gitlab-shell!548

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.4
-golang 1.16.10
+golang 1.16.12
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1640272000 -3600
#      Thu Dec 23 16:06:40 2021 +0100
# Branch heptapod
# Node ID 500ed5c3c7f5402868765b8f4e8794a84df9b0e4
# Parent  9e659daeffdf3b711ef6aaba6cf4c737bb9f632c
# Parent  20c9325c66f15b775f94cd0b11bb6df45d9510db
Merged upstream v13.22.1 into heptapod branch

The parts of `internal/command/command.go` that were changed
in Heptapod Shell got moved by upstream got
`cmd/gitlab-shell/command.go`, so we had to follow suit.

Everything else was straightforward.

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -45,7 +45,7 @@
     - go version
     - which go
   services:
-    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:latest
+    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:master
       # Disable the hooks so we don't have to stub the GitLab API
       command: ["bash", "-c", "mkdir -p /home/git/repositories && rm -rf /srv/gitlab-shell/hooks/* && exec /usr/bin/env GITALY_TESTING_NO_GIT_HOOKS=1 /scripts/process-wrapper"]
       alias: gitaly
diff --git a/.ruby-version b/.ruby-version
--- a/.ruby-version
+++ b/.ruby-version
@@ -1,1 +1,1 @@
-2.7.2
+2.7.4
diff --git a/.tool-versions b/.tool-versions
new file mode 100644
--- /dev/null
+++ b/.tool-versions
@@ -0,0 +1,2 @@
+ruby 2.7.4
+golang 1.16.10
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,9 +1,40 @@
+v13.22.1
+
+- Remove SSL_CERT_DIR logging !546
+
+v13.22.0
+
+- Relax key and username matching for sshd !540
+- Add logging to handler/exec.go and config/config.go !539
+- Improve logging for non-git commands !538
+- Update to Go v1.16.9 !537
+- Reject non-proxied connections when proxy protocol is enabled !536
+- Log command invocation !535
+- Fix logging channel type !534
+- Resolve an error-swallowing issue !533
+- Add more logging to gitlab-sshd !531
+- Respect log-level configuration again !530
+- Improve err message given when Gitaly unavailable !526
+- makefile: properly escape '$' in VERSION_STRING !525
+- Add context fields to logging !524
+- Extract server config related code out of sshd.go !523
+- Add TestInvalidClientConfig and TestNewServerWithoutHosts for sshd.go !518
+- Update Ruby version to 2.7.4 and add Go version 1.16.8 for tooling !517
+
 v13.21.1
 
 - Only validate SSL cert file exists if a value is supplied !527
 
 v13.21.0
 
+- Switch to labkit for logging system setup !504
+- Remove some unreliable tests !503
+- Make gofmt check fail if there are any matching files !500
+- Update go-proxyproto to v0.6.0 !499
+- Switch to labkit/log for logging functionality !498
+- Unit tests for internal/sshd/connection.go !497
+- Prometheus metrics for HTTP requests !496
+- Refactor testhelper.PrepareTestRootDir using t.Cleanup !493
 - Change default logging format to JSON !476
 - Shutdown sshd gracefully !484
 - Provide liveness and readiness probes !494
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -2,12 +2,13 @@
 
 GO_SOURCES := $(shell find . -name '*.go')
 VERSION_STRING := $(shell ./compute_version || echo unknown)
-BUILD_TIME := $(shell date -u +%Y%m%d.%H%M%S)
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)"
 
 PREFIX ?= /usr/local
 
+build: bin/gitlab-shell
+
 validate: verify test
 
 verify: verify_golang
@@ -40,7 +41,6 @@
 _script_install:
 	bin/install
 
-build: bin/gitlab-shell
 compile: bin/gitlab-shell
 bin/gitlab-shell: $(GO_SOURCES)
 	GOBIN="$(CURDIR)/bin" go install $(GOBUILD_FLAGS) ./cmd/...
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -102,6 +102,21 @@
 
 Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
 
+## Logging Guidelines
+
+In general, it should be possible to determine the structure, but not content,
+of a gitlab-shell or gitlab-sshd session just from inspecting the logs. Some
+guidelines:
+
+- We use [`gitlab.com/gitlab-org/labkit/log`](https://pkg.go.dev/gitlab.com/gitlab-org/labkit/log)
+  for logging functionality
+- **Always** include a correlation ID
+- Log messages should be invariant and unique. Include accessory information in
+  fields, using `log.WithField`, `log.WithFields`, or `log.WithError`.
+- Log success cases as well as error cases
+- Logging too much is better than not logging enough. If a message seems too
+  verbose, consider reducing the log level before removing the message.
+
 ## Releasing
 
 See [PROCESS.md](./PROCESS.md)
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.21.1
+13.22.1
diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -5,7 +5,7 @@
 	"encoding/base64"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"path"
 	"strings"
@@ -83,7 +83,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, string(responseBody), "Hello")
 	})
@@ -99,7 +99,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, "Echo: {\"key\":\"value\"}", string(responseBody))
 	})
@@ -155,7 +155,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
@@ -170,7 +170,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
@@ -194,7 +194,7 @@
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				require.Equal(t, http.MethodPost, r.Method)
 
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -6,7 +6,6 @@
 	"crypto/x509"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"os"
@@ -153,7 +152,7 @@
 	}
 
 	if hcc.caPath != "" {
-		fis, _ := ioutil.ReadDir(hcc.caPath)
+		fis, _ := os.ReadDir(hcc.caPath)
 		for _, fi := range fis {
 			if fi.IsDir() {
 				continue
@@ -174,7 +173,6 @@
 			return nil, "", err
 		}
 		tlsConfig.Certificates = []tls.Certificate{cert}
-		tlsConfig.BuildNameToCertificate()
 	}
 
 	transport := &http.Transport{
@@ -185,7 +183,7 @@
 }
 
 func addCertToPool(certPool *x509.CertPool, fileName string) {
-	cert, err := ioutil.ReadFile(fileName)
+	cert, err := os.ReadFile(fileName)
 	if err == nil {
 		certPool.AppendCertsFromPEM(cert)
 	}
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -4,7 +4,7 @@
 	"context"
 	"encoding/base64"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strings"
 	"testing"
@@ -64,7 +64,7 @@
 	defer response.Body.Close()
 
 	require.NotNil(t, response)
-	responseBody, err := ioutil.ReadAll(response.Body)
+	responseBody, err := io.ReadAll(response.Body)
 	require.NoError(t, err)
 
 	headerParts := strings.Split(string(responseBody), " ")
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"path"
 	"testing"
@@ -57,7 +57,7 @@
 
 			defer response.Body.Close()
 
-			responseBody, err := ioutil.ReadAll(response.Body)
+			responseBody, err := io.ReadAll(response.Body)
 			require.NoError(t, err)
 			require.Equal(t, string(responseBody), "Hello")
 		})
diff --git a/client/testserver/gitalyserver.go b/client/testserver/gitalyserver.go
--- a/client/testserver/gitalyserver.go
+++ b/client/testserver/gitalyserver.go
@@ -1,7 +1,6 @@
 package testserver
 
 import (
-	"io/ioutil"
 	"net"
 	"os"
 	"path"
@@ -61,7 +60,7 @@
 func StartGitalyServer(t *testing.T) (string, *TestGitalyServer) {
 	t.Helper()
 
-	tempDir, _ := ioutil.TempDir("", "gitlab-shell-test-api")
+	tempDir, _ := os.MkdirTemp("", "gitlab-shell-test-api")
 	gitalySocketPath := path.Join(tempDir, "gitaly.sock")
 	t.Cleanup(func() { os.RemoveAll(tempDir) })
 
diff --git a/client/testserver/testserver.go b/client/testserver/testserver.go
--- a/client/testserver/testserver.go
+++ b/client/testserver/testserver.go
@@ -3,7 +3,7 @@
 import (
 	"crypto/tls"
 	"crypto/x509"
-	"io/ioutil"
+	"io"
 	"log"
 	"net"
 	"net/http"
@@ -18,7 +18,7 @@
 )
 
 var (
-	tempDir, _ = ioutil.TempDir("", "gitlab-shell-test-api")
+	tempDir, _ = os.MkdirTemp("", "gitlab-shell-test-api")
 	testSocket = path.Join(tempDir, "internal.sock")
 )
 
@@ -41,7 +41,7 @@
 		Handler: buildHandler(handlers),
 		// We'll put this server through some nasty stuff we don't want
 		// in our test output
-		ErrorLog: log.New(ioutil.Discard, "", 0),
+		ErrorLog: log.New(io.Discard, "", 0),
 	}
 	go server.Serve(socketListener)
 
@@ -73,10 +73,9 @@
 		Certificates: []tls.Certificate{cer},
 		MinVersion:   tls.VersionTLS12,
 	}
-	server.TLS.BuildNameToCertificate()
 
 	if clientCAPath != "" {
-		caCert, err := ioutil.ReadFile(clientCAPath)
+		caCert, err := os.ReadFile(clientCAPath)
 		require.NoError(t, err)
 
 		caCertPool := x509.NewCertPool()
diff --git a/cmd/check/command/command.go b/cmd/check/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/check/command/command.go
@@ -0,0 +1,21 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+)
+
+func New(config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	if cmd := build(config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func build(config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	return &healthcheck.Command{Config: config, ReadWriter: readWriter}
+}
diff --git a/cmd/check/command/command_test.go b/cmd/check/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/check/command/command_test.go
@@ -0,0 +1,42 @@
+package command_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	basicConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a Healthcheck command",
+			config:       basicConfig,
+			expectedType: &healthcheck.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := command.New(tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
diff --git a/cmd/check/main.go b/cmd/check/main.go
--- a/cmd/check/main.go
+++ b/cmd/check/main.go
@@ -4,12 +4,12 @@
 	"fmt"
 	"os"
 
+	checkCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
 func main() {
@@ -34,7 +34,7 @@
 	logCloser := logger.Configure(config)
 	defer logCloser.Close()
 
-	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
+	cmd, err := checkCmd.New(config, readWriter)
 	if err != nil {
 		fmt.Fprintf(readWriter.ErrOut, "%v\n", err)
 		os.Exit(1)
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command.go b/cmd/gitlab-shell-authorized-keys-check/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command.go
@@ -0,0 +1,37 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+)
+
+func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(arguments)
+	if err != nil {
+		return nil, err
+	}
+
+	if cmd := build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func Parse(arguments []string) (*commandargs.AuthorizedKeys, error) {
+	args := &commandargs.AuthorizedKeys{Arguments: arguments}
+
+	if err := args.Parse(); err != nil {
+		return nil, err
+	}
+
+	return args, nil
+}
+
+func build(args *commandargs.AuthorizedKeys, config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	return &authorizedkeys.Command{Config: config, Args: args, ReadWriter: readWriter}
+}
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command_test.go b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
@@ -0,0 +1,120 @@
+package command_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	authorizedKeysExec = &executable.Executable{Name: executable.AuthorizedKeysCheck}
+	basicConfig        = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a AuthorizedKeys command",
+			executable:   authorizedKeysExec,
+			arguments:    []string{"git", "git", "key"},
+			config:       basicConfig,
+			expectedType: &authorizedkeys.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := command.New(tc.arguments, tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
+
+func TestParseSuccess(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		expectedArgs commandargs.CommandArgs
+		expectError  bool
+	}{
+		{
+			desc:         "It parses authorized-keys command",
+			executable:   &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:    []string{"git", "git", "key"},
+			expectedArgs: &commandargs.AuthorizedKeys{Arguments: []string{"git", "git", "key"}, ExpectedUser: "git", ActualUser: "git", Key: "key"},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := command.Parse(tc.arguments)
+
+			if !tc.expectError {
+				require.NoError(t, err)
+				require.Equal(t, tc.expectedArgs, result)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
+
+func TestParseFailure(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		arguments     []string
+		expectedError string
+	}{
+		{
+			desc:          "With not enough arguments for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user"},
+			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
+		},
+		{
+			desc:          "With too many arguments for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user", "user", "key", "something-else"},
+			expectedError: "# Insufficient arguments. 4. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
+		},
+		{
+			desc:          "With missing username for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user", "", "key"},
+			expectedError: "# No username provided",
+		},
+		{
+			desc:          "With missing key for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user", "user", ""},
+			expectedError: "# No key provided",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err := command.Parse(tc.arguments)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go
--- a/cmd/gitlab-shell-authorized-keys-check/main.go
+++ b/cmd/gitlab-shell-authorized-keys-check/main.go
@@ -4,13 +4,13 @@
 	"fmt"
 	"os"
 
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
 func main() {
@@ -35,7 +35,7 @@
 	logCloser := logger.Configure(config)
 	defer logCloser.Close()
 
-	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
+	cmd, err := cmd.New(os.Args[1:], config, readWriter)
 	if err != nil {
 		// For now this could happen if `SSH_CONNECTION` is not set on
 		// the environment
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command.go b/cmd/gitlab-shell-authorized-principals-check/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command.go
@@ -0,0 +1,37 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+)
+
+func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(arguments)
+	if err != nil {
+		return nil, err
+	}
+
+	if cmd := build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func Parse(arguments []string) (*commandargs.AuthorizedPrincipals, error) {
+	args := &commandargs.AuthorizedPrincipals{Arguments: arguments}
+
+	if err := args.Parse(); err != nil {
+		return nil, err
+	}
+
+	return args, nil
+}
+
+func build(args *commandargs.AuthorizedPrincipals, config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	return &authorizedprincipals.Command{Config: config, Args: args, ReadWriter: readWriter}
+}
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command_test.go b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
@@ -0,0 +1,114 @@
+package command_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	authorizedPrincipalsExec = &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}
+	basicConfig              = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a AuthorizedPrincipals command",
+			executable:   authorizedPrincipalsExec,
+			arguments:    []string{"key", "principal"},
+			config:       basicConfig,
+			expectedType: &authorizedprincipals.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := cmd.New(tc.arguments, tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
+
+func TestParseSuccess(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		expectedArgs commandargs.CommandArgs
+		expectError  bool
+	}{
+		{
+			desc:         "It parses authorized-principals command",
+			executable:   &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:    []string{"key", "principal-1", "principal-2"},
+			expectedArgs: &commandargs.AuthorizedPrincipals{Arguments: []string{"key", "principal-1", "principal-2"}, KeyId: "key", Principals: []string{"principal-1", "principal-2"}},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := cmd.Parse(tc.arguments)
+
+			if !tc.expectError {
+				require.NoError(t, err)
+				require.Equal(t, tc.expectedArgs, result)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
+
+func TestParseFailure(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		arguments     []string
+		expectedError string
+	}{
+		{
+			desc:          "With not enough arguments for the AuthorizedPrincipalsCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:     []string{"key"},
+			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-principals-check <key-id> <principal1> [<principal2>...]",
+		},
+		{
+			desc:          "With missing key_id for the AuthorizedPrincipalsCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:     []string{"", "principal"},
+			expectedError: "# No key_id provided",
+		},
+		{
+			desc:          "With blank principal for the AuthorizedPrincipalsCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:     []string{"key", "principal", ""},
+			expectedError: "# An invalid principal was provided",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err := cmd.Parse(tc.arguments)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go
--- a/cmd/gitlab-shell-authorized-principals-check/main.go
+++ b/cmd/gitlab-shell-authorized-principals-check/main.go
@@ -4,13 +4,13 @@
 	"fmt"
 	"os"
 
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
 func main() {
@@ -35,7 +35,7 @@
 	logCloser := logger.Configure(config)
 	defer logCloser.Close()
 
-	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
+	cmd, err := cmd.New(os.Args[1:], config, readWriter)
 	if err != nil {
 		// For now this could happen if `SSH_CONNECTION` is not set on
 		// the environment
diff --git a/cmd/gitlab-shell/command/command.go b/cmd/gitlab-shell/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell/command/command.go
@@ -0,0 +1,81 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/hg"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+func New(arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(arguments, env)
+	if err != nil {
+		return nil, err
+	}
+
+	if cmd := Build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func NewWithKey(gitlabKeyId string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(nil, env)
+	if err != nil {
+		return nil, err
+	}
+
+	args.GitlabKeyId = gitlabKeyId
+	if cmd := Build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func Parse(arguments []string, env sshenv.Env) (*commandargs.Shell, error) {
+	args := &commandargs.Shell{Arguments: arguments, Env: env}
+
+	if err := args.Parse(); err != nil {
+		return nil, err
+	}
+
+	return args, nil
+}
+
+func Build(args *commandargs.Shell, config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	switch args.CommandType {
+	case commandargs.Discover:
+		return &discover.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.TwoFactorRecover:
+		return &twofactorrecover.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.Hg:
+		return &hg.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.TwoFactorVerify:
+		return &twofactorverify.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.LfsAuthenticate:
+		return &lfsauthenticate.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.ReceivePack:
+		return &receivepack.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.UploadPack:
+		return &uploadpack.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.UploadArchive:
+		return &uploadarchive.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.PersonalAccessToken:
+		return &personalaccesstoken.Command{Config: config, Args: args, ReadWriter: readWriter}
+	}
+
+	return nil
+}
diff --git a/cmd/gitlab-shell/command/command_test.go b/cmd/gitlab-shell/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell/command/command_test.go
@@ -0,0 +1,302 @@
+package command_test
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	gitlabShellExec = &executable.Executable{Name: executable.GitlabShell}
+	basicConfig     = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a Discover command",
+			executable:   gitlabShellExec,
+			env:          buildEnv(""),
+			config:       basicConfig,
+			expectedType: &discover.Command{},
+		},
+		{
+			desc:         "it returns a TwoFactorRecover command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("2fa_recovery_codes"),
+			config:       basicConfig,
+			expectedType: &twofactorrecover.Command{},
+		},
+		{
+			desc:         "it returns a TwoFactorVerify command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("2fa_verify"),
+			config:       basicConfig,
+			expectedType: &twofactorverify.Command{},
+		},
+		{
+			desc:         "it returns an LfsAuthenticate command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-lfs-authenticate"),
+			config:       basicConfig,
+			expectedType: &lfsauthenticate.Command{},
+		},
+		{
+			desc:         "it returns a ReceivePack command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-receive-pack"),
+			config:       basicConfig,
+			expectedType: &receivepack.Command{},
+		},
+		{
+			desc:         "it returns an UploadPack command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-upload-pack"),
+			config:       basicConfig,
+			expectedType: &uploadpack.Command{},
+		},
+		{
+			desc:         "it returns an UploadArchive command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-upload-archive"),
+			config:       basicConfig,
+			expectedType: &uploadarchive.Command{},
+		},
+		{
+			desc:         "it returns a PersonalAccessToken command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("personal_access_token"),
+			config:       basicConfig,
+			expectedType: &personalaccesstoken.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := cmd.New(tc.arguments, tc.env, tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
+
+func TestFailingNew(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		expectedError error
+	}{
+		{
+			desc:          "Parsing environment failed",
+			executable:    gitlabShellExec,
+			expectedError: errors.New("Only SSH allowed"),
+		},
+		{
+			desc:          "Unknown command given",
+			executable:    gitlabShellExec,
+			env:           buildEnv("unknown"),
+			expectedError: disallowedcommand.Error,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := cmd.New([]string{}, tc.env, basicConfig, nil)
+			require.Nil(t, command)
+			require.Equal(t, tc.expectedError, err)
+		})
+	}
+}
+
+func buildEnv(command string) sshenv.Env {
+	return sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: command,
+	}
+}
+
+func TestParseSuccess(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		expectedArgs commandargs.CommandArgs
+		expectError  bool
+	}{
+		{
+			desc:         "It sets discover as the command when the command string was empty",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{}, CommandType: commandargs.Discover, Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the key id in any passed arguments",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the key id only if the argument is of <key-id> format",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "username-key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "username-key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the key id if the key is listed as the last argument",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "gitlab-shell -c key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "gitlab-shell -c key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the username if the username is listed as the last argument",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "gitlab-shell -c username-jane-doe"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "gitlab-shell -c username-jane-doe"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "jane-doe", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the key id only if the last argument is of <key-id> format",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "gitlab-shell -c username-key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "gitlab-shell -c username-key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the username in any passed arguments",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "username-jane-doe"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "username-jane-doe"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "jane-doe", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It parses 2fa_recovery_codes command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"2fa_recovery_codes"}, CommandType: commandargs.TwoFactorRecover, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"}},
+		},
+		{
+			desc:         "It parses git-receive-pack command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"}},
+		},
+		{
+			desc:         "It parses git-receive-pack command and a project with single quotes",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"}},
+		},
+		{
+			desc:         `It parses "git receive-pack" command`,
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`}},
+		},
+		{
+			desc:         `It parses a command followed by control characters`,
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`}},
+		},
+		{
+			desc:         "It parses git-upload-pack command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-upload-pack", "group/repo"}, CommandType: commandargs.UploadPack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`}},
+		},
+		{
+			desc:         "It parses git-upload-archive command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-upload-archive", "group/repo"}, CommandType: commandargs.UploadArchive, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"}},
+		},
+		{
+			desc:         "It parses git-lfs-authenticate command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-lfs-authenticate", "group/repo", "download"}, CommandType: commandargs.LfsAuthenticate, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"}},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := cmd.Parse(tc.arguments, tc.env)
+
+			if !tc.expectError {
+				require.NoError(t, err)
+				require.Equal(t, tc.expectedArgs, result)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
+
+func TestParseFailure(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		arguments     []string
+		expectedError string
+	}{
+		{
+			desc:          "It fails if SSH connection is not set",
+			executable:    &executable.Executable{Name: executable.GitlabShell},
+			arguments:     []string{},
+			expectedError: "Only SSH allowed",
+		},
+		{
+			desc:          "It fails if SSH command is invalid",
+			executable:    &executable.Executable{Name: executable.GitlabShell},
+			env:           sshenv.Env{IsSSHConnection: true, OriginalCommand: `git receive-pack "`},
+			arguments:     []string{},
+			expectedError: "Invalid SSH command: invalid command line string",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err := cmd.Parse(tc.arguments, tc.env)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -3,7 +3,11 @@
 import (
 	"fmt"
 	"os"
+	"reflect"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -50,7 +54,7 @@
 	defer logCloser.Close()
 
 	env := sshenv.NewFromEnv()
-	cmd, err := command.New(executable, os.Args[1:], env, config, readWriter)
+	cmd, err := shellCmd.New(os.Args[1:], env, config, readWriter)
 	if err != nil {
 		// For now this could happen if `SSH_CONNECTION` is not set on
 		// the environment
@@ -61,8 +65,15 @@
 	ctx, finished := command.Setup(executable.Name, config)
 	defer finished()
 
-	if err = cmd.Execute(ctx); err != nil {
+	cmdName := reflect.TypeOf(cmd).String()
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("gitlab-shell: main: executing command")
+
+	if err := cmd.Execute(ctx); err != nil {
+		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
 		console.DisplayWarningMessage(err.Error(), readWriter.ErrOut)
 		os.Exit(1)
 	}
+
+	ctxlog.Info("gitlab-shell: main: command executed successfully")
 }
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -8,7 +8,6 @@
 	"encoding/pem"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"net/http/httptest"
@@ -112,7 +111,7 @@
 		case "/api/v4/internal/two_factor_otp_check":
 			fmt.Fprint(w, `{"success": true}`)
 		case "/api/v4/internal/allowed":
-			body, err := ioutil.ReadFile(filepath.Join(testhelper.TestRoot, "responses/allowed_without_console_messages.json"))
+			body, err := os.ReadFile(filepath.Join(testhelper.TestRoot, "responses/allowed_without_console_messages.json"))
 			require.NoError(t, err)
 
 			response := strings.Replace(string(body), "GITALY_REPOSITORY", testRepo, 1)
@@ -198,7 +197,7 @@
 func configureSSHD(t *testing.T, apiServer string) (string, ed25519.PublicKey) {
 	t.Helper()
 
-	dir, err := ioutil.TempDir("", "gitlab-sshd-acceptance-test-")
+	dir, err := os.MkdirTemp("", "gitlab-sshd-acceptance-test-")
 	require.NoError(t, err)
 	t.Cleanup(func() { os.RemoveAll(dir) })
 
@@ -209,11 +208,11 @@
 	require.NoError(t, err)
 
 	configFileData := genServerConfig(apiServer, hostKeyFile)
-	require.NoError(t, ioutil.WriteFile(configFile, configFileData, 0644))
+	require.NoError(t, os.WriteFile(configFile, configFileData, 0644))
 
 	block := &pem.Block{Type: "OPENSSH PRIVATE KEY", Bytes: edkey.MarshalED25519PrivateKey(priv)}
 	hostKeyData := pem.EncodeToMemory(block)
-	require.NoError(t, ioutil.WriteFile(hostKeyFile, hostKeyData, 0400))
+	require.NoError(t, os.WriteFile(hostKeyFile, hostKeyData, 0400))
 
 	return dir, pub
 }
@@ -326,7 +325,7 @@
 	_, err = fmt.Fprintln(stdin, "yes")
 	require.NoError(t, err)
 
-	output, err := ioutil.ReadAll(stdout)
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 	require.Equal(t, `
 Your two-factor authentication recovery codes are:
@@ -365,7 +364,7 @@
 	_, err = fmt.Fprintln(stdin, "otp123")
 	require.NoError(t, err)
 
-	output, err := ioutil.ReadAll(stdout)
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 	require.Equal(t, "OTP validation successful. Git operations are now allowed.\n", string(output))
 }
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -97,7 +97,7 @@
 		sig := <-done
 		signal.Reset(syscall.SIGINT, syscall.SIGTERM)
 
-		log.WithFields(log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
+		log.WithContextFields(ctx, log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
 
 		server.Shutdown()
 
diff --git a/docker-compose.yml b/docker-compose.yml
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -3,6 +3,6 @@
   gitaly:
     environment:
       - GITALY_TESTING_NO_GIT_HOOKS=1
-    image: registry.gitlab.com/gitlab-org/build/cng/gitaly:latest
+    image: registry.gitlab.com/gitlab-org/build/cng/gitaly:master
     ports:
       - '8075:8075'
diff --git a/internal/command/command.go b/internal/command/command.go
--- a/internal/command/command.go
+++ b/internal/command/command.go
@@ -3,24 +3,7 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/hg"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/tracing"
 )
@@ -29,19 +12,6 @@
 	Execute(ctx context.Context) error
 }
 
-func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (Command, error) {
-	args, err := commandargs.Parse(e, arguments, env)
-	if err != nil {
-		return nil, err
-	}
-
-	if cmd := buildCommand(e, args, config, readWriter); cmd != nil {
-		return cmd, nil
-	}
-
-	return nil, disallowedcommand.Error
-}
-
 // Setup() initializes tracing from the configuration file and generates a
 // background context from which all other contexts in the process should derive
 // from, as it has a service name and initial correlation ID set.
@@ -77,55 +47,3 @@
 		closer.Close()
 	}
 }
-
-func buildCommand(e *executable.Executable, args commandargs.CommandArgs, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	switch e.Name {
-	case executable.GitlabShell:
-		return BuildShellCommand(args.(*commandargs.Shell), config, readWriter)
-	case executable.AuthorizedKeysCheck:
-		return buildAuthorizedKeysCommand(args.(*commandargs.AuthorizedKeys), config, readWriter)
-	case executable.AuthorizedPrincipalsCheck:
-		return buildAuthorizedPrincipalsCommand(args.(*commandargs.AuthorizedPrincipals), config, readWriter)
-	case executable.Healthcheck:
-		return buildHealthcheckCommand(config, readWriter)
-	}
-
-	return nil
-}
-
-func BuildShellCommand(args *commandargs.Shell, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	switch args.CommandType {
-	case commandargs.Discover:
-		return &discover.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.TwoFactorRecover:
-		return &twofactorrecover.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.TwoFactorVerify:
-		return &twofactorverify.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.Hg:
-		return &hg.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.LfsAuthenticate:
-		return &lfsauthenticate.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.ReceivePack:
-		return &receivepack.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.UploadPack:
-		return &uploadpack.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.UploadArchive:
-		return &uploadarchive.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.PersonalAccessToken:
-		return &personalaccesstoken.Command{Config: config, Args: args, ReadWriter: readWriter}
-	}
-
-	return nil
-}
-
-func buildAuthorizedKeysCommand(args *commandargs.AuthorizedKeys, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	return &authorizedkeys.Command{Config: config, Args: args, ReadWriter: readWriter}
-}
-
-func buildAuthorizedPrincipalsCommand(args *commandargs.AuthorizedPrincipals, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	return &authorizedprincipals.Command{Config: config, Args: args, ReadWriter: readWriter}
-}
-
-func buildHealthcheckCommand(config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	return &healthcheck.Command{Config: config, ReadWriter: readWriter}
-}
diff --git a/internal/command/command_test.go b/internal/command/command_test.go
--- a/internal/command/command_test.go
+++ b/internal/command/command_test.go
@@ -1,173 +1,15 @@
 package command
 
 import (
-	"errors"
 	"os"
 	"testing"
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
-var (
-	authorizedKeysExec       = &executable.Executable{Name: executable.AuthorizedKeysCheck}
-	authorizedPrincipalsExec = &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}
-	checkExec                = &executable.Executable{Name: executable.Healthcheck}
-	gitlabShellExec          = &executable.Executable{Name: executable.GitlabShell}
-
-	basicConfig    = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
-	advancedConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket", SslCertDir: "/tmp/certs"}
-)
-
-func buildEnv(command string) sshenv.Env {
-	return sshenv.Env{
-		IsSSHConnection: true,
-		OriginalCommand: command,
-	}
-}
-
-func TestNew(t *testing.T) {
-	testCases := []struct {
-		desc         string
-		executable   *executable.Executable
-		env          sshenv.Env
-		arguments    []string
-		config       *config.Config
-		expectedType interface{}
-	}{
-		{
-			desc:         "it returns a Discover command",
-			executable:   gitlabShellExec,
-			env:          buildEnv(""),
-			config:       basicConfig,
-			expectedType: &discover.Command{},
-		},
-		{
-			desc:         "it returns a TwoFactorRecover command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("2fa_recovery_codes"),
-			config:       basicConfig,
-			expectedType: &twofactorrecover.Command{},
-		},
-		{
-			desc:         "it returns a TwoFactorVerify command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("2fa_verify"),
-			config:       basicConfig,
-			expectedType: &twofactorverify.Command{},
-		},
-		{
-			desc:         "it returns an LfsAuthenticate command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-lfs-authenticate"),
-			config:       basicConfig,
-			expectedType: &lfsauthenticate.Command{},
-		},
-		{
-			desc:         "it returns a ReceivePack command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-receive-pack"),
-			config:       basicConfig,
-			expectedType: &receivepack.Command{},
-		},
-		{
-			desc:         "it returns an UploadPack command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-upload-pack"),
-			config:       basicConfig,
-			expectedType: &uploadpack.Command{},
-		},
-		{
-			desc:         "it returns an UploadArchive command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-upload-archive"),
-			config:       basicConfig,
-			expectedType: &uploadarchive.Command{},
-		},
-		{
-			desc:         "it returns a Healthcheck command",
-			executable:   checkExec,
-			config:       basicConfig,
-			expectedType: &healthcheck.Command{},
-		},
-		{
-			desc:         "it returns a AuthorizedKeys command",
-			executable:   authorizedKeysExec,
-			arguments:    []string{"git", "git", "key"},
-			config:       basicConfig,
-			expectedType: &authorizedkeys.Command{},
-		},
-		{
-			desc:         "it returns a AuthorizedPrincipals command",
-			executable:   authorizedPrincipalsExec,
-			arguments:    []string{"key", "principal"},
-			config:       basicConfig,
-			expectedType: &authorizedprincipals.Command{},
-		},
-		{
-			desc:         "it returns a PersonalAccessToken command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("personal_access_token"),
-			config:       basicConfig,
-			expectedType: &personalaccesstoken.Command{},
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			command, err := New(tc.executable, tc.arguments, tc.env, tc.config, nil)
-
-			require.NoError(t, err)
-			require.IsType(t, tc.expectedType, command)
-		})
-	}
-}
-
-func TestFailingNew(t *testing.T) {
-	testCases := []struct {
-		desc          string
-		executable    *executable.Executable
-		env           sshenv.Env
-		expectedError error
-	}{
-		{
-			desc:          "Parsing environment failed",
-			executable:    gitlabShellExec,
-			expectedError: errors.New("Only SSH allowed"),
-		},
-		{
-			desc:          "Unknown command given",
-			executable:    gitlabShellExec,
-			env:           buildEnv("unknown"),
-			expectedError: disallowedcommand.Error,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			command, err := New(tc.executable, []string{}, tc.env, basicConfig, nil)
-			require.Nil(t, command)
-			require.Equal(t, tc.expectedError, err)
-		})
-	}
-}
-
 func TestSetup(t *testing.T) {
 	testCases := []struct {
 		name                  string
diff --git a/internal/command/commandargs/command_args.go b/internal/command/commandargs/command_args.go
--- a/internal/command/commandargs/command_args.go
+++ b/internal/command/commandargs/command_args.go
@@ -1,32 +1,8 @@
 package commandargs
 
-import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-)
-
 type CommandType string
 
 type CommandArgs interface {
 	Parse() error
 	GetArguments() []string
 }
-
-func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (CommandArgs, error) {
-	var args CommandArgs = &GenericArgs{Arguments: arguments}
-
-	switch e.Name {
-	case executable.GitlabShell:
-		args = &Shell{Arguments: arguments, Env: env}
-	case executable.AuthorizedKeysCheck:
-		args = &AuthorizedKeys{Arguments: arguments}
-	case executable.AuthorizedPrincipalsCheck:
-		args = &AuthorizedPrincipals{Arguments: arguments}
-	}
-
-	if err := args.Parse(); err != nil {
-		return nil, err
-	}
-
-	return args, nil
-}
diff --git a/internal/command/commandargs/command_args_test.go b/internal/command/commandargs/command_args_test.go
deleted file mode 100644
--- a/internal/command/commandargs/command_args_test.go
+++ /dev/null
@@ -1,192 +0,0 @@
-package commandargs
-
-import (
-	"testing"
-
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestParseSuccess(t *testing.T) {
-	testCases := []struct {
-		desc         string
-		executable   *executable.Executable
-		env          sshenv.Env
-		arguments    []string
-		expectedArgs CommandArgs
-	}{
-		{
-			desc:         "It sets discover as the command when the command string was empty",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{}, CommandType: Discover, Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It finds the key id in any passed arguments",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{"hello", "key-123"},
-			expectedArgs: &Shell{Arguments: []string{"hello", "key-123"}, SshArgs: []string{}, CommandType: Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It finds the key id only if the argument is of <key-id> format",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{"hello", "username-key-123"},
-			expectedArgs: &Shell{Arguments: []string{"hello", "username-key-123"}, SshArgs: []string{}, CommandType: Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It finds the username in any passed arguments",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{"hello", "username-jane-doe"},
-			expectedArgs: &Shell{Arguments: []string{"hello", "username-jane-doe"}, SshArgs: []string{}, CommandType: Discover, GitlabUsername: "jane-doe", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It parses 2fa_recovery_codes command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"2fa_recovery_codes"}, CommandType: TwoFactorRecover, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"}},
-		}, {
-			desc:         "It parses git-receive-pack command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"}},
-		}, {
-			desc:         "It parses git-receive-pack command and a project with single quotes",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"}},
-		}, {
-			desc:         `It parses "git receive-pack" command`,
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`}},
-		}, {
-			desc:         `It parses a command followed by control characters`,
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`}},
-		}, {
-			desc:         "It parses git-upload-pack command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-upload-pack", "group/repo"}, CommandType: UploadPack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`}},
-		}, {
-			desc:         "It parses git-upload-archive command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-upload-archive", "group/repo"}, CommandType: UploadArchive, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"}},
-		}, {
-			desc:         "It parses git-lfs-authenticate command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-lfs-authenticate", "group/repo", "download"}, CommandType: LfsAuthenticate, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"}},
-		}, {
-			desc:         "It parses authorized-keys command",
-			executable:   &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:    []string{"git", "git", "key"},
-			expectedArgs: &AuthorizedKeys{Arguments: []string{"git", "git", "key"}, ExpectedUser: "git", ActualUser: "git", Key: "key"},
-		}, {
-			desc:         "It parses authorized-principals command",
-			executable:   &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:    []string{"key", "principal-1", "principal-2"},
-			expectedArgs: &AuthorizedPrincipals{Arguments: []string{"key", "principal-1", "principal-2"}, KeyId: "key", Principals: []string{"principal-1", "principal-2"}},
-		}, {
-			desc:         "Unknown executable",
-			executable:   &executable.Executable{Name: "unknown"},
-			arguments:    []string{},
-			expectedArgs: &GenericArgs{Arguments: []string{}},
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			result, err := Parse(tc.executable, tc.arguments, tc.env)
-
-			require.NoError(t, err)
-			require.Equal(t, tc.expectedArgs, result)
-		})
-	}
-}
-
-func TestParseFailure(t *testing.T) {
-	testCases := []struct {
-		desc          string
-		executable    *executable.Executable
-		env           sshenv.Env
-		arguments     []string
-		expectedError string
-	}{
-		{
-			desc:          "It fails if SSH connection is not set",
-			executable:    &executable.Executable{Name: executable.GitlabShell},
-			arguments:     []string{},
-			expectedError: "Only SSH allowed",
-		},
-		{
-			desc:          "It fails if SSH command is invalid",
-			executable:    &executable.Executable{Name: executable.GitlabShell},
-			env:           sshenv.Env{IsSSHConnection: true, OriginalCommand: `git receive-pack "`},
-			arguments:     []string{},
-			expectedError: "Invalid SSH command",
-		},
-		{
-			desc:          "With not enough arguments for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user"},
-			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
-		},
-		{
-			desc:          "With too many arguments for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user", "user", "key", "something-else"},
-			expectedError: "# Insufficient arguments. 4. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
-		},
-		{
-			desc:          "With missing username for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user", "", "key"},
-			expectedError: "# No username provided",
-		},
-		{
-			desc:          "With missing key for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user", "user", ""},
-			expectedError: "# No key provided",
-		},
-		{
-			desc:          "With not enough arguments for the AuthorizedPrincipalsCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:     []string{"key"},
-			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-principals-check <key-id> <principal1> [<principal2>...]",
-		},
-		{
-			desc:          "With missing key_id for the AuthorizedPrincipalsCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:     []string{"", "principal"},
-			expectedError: "# No key_id provided",
-		},
-		{
-			desc:          "With blank principal for the AuthorizedPrincipalsCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:     []string{"key", "principal", ""},
-			expectedError: "# An invalid principal was provided",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			_, err := Parse(tc.executable, tc.arguments, tc.env)
-
-			require.EqualError(t, err, tc.expectedError)
-		})
-	}
-}
diff --git a/internal/command/commandargs/generic_args.go b/internal/command/commandargs/generic_args.go
deleted file mode 100644
--- a/internal/command/commandargs/generic_args.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package commandargs
-
-type GenericArgs struct {
-	Arguments []string
-}
-
-func (b *GenericArgs) Parse() error {
-	// Do nothing
-	return nil
-}
-
-func (b *GenericArgs) GetArguments() []string {
-	return b.Arguments
-}
diff --git a/internal/command/commandargs/shell.go b/internal/command/commandargs/shell.go
--- a/internal/command/commandargs/shell.go
+++ b/internal/command/commandargs/shell.go
@@ -1,8 +1,9 @@
 package commandargs
 
 import (
-	"errors"
+	"fmt"
 	"regexp"
+	"strings"
 
 	"github.com/mattn/go-shellwords"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
@@ -50,21 +51,16 @@
 
 func (s *Shell) validate() error {
 	if !s.Env.IsSSHConnection {
-		return errors.New("Only SSH allowed")
+		return fmt.Errorf("Only SSH allowed")
 	}
 
-	if !s.isValidSSHCommand() {
-		return errors.New("Invalid SSH command")
+	if err := s.ParseCommand(s.Env.OriginalCommand); err != nil {
+		return fmt.Errorf("Invalid SSH command: %w", err)
 	}
 
 	return nil
 }
 
-func (s *Shell) isValidSSHCommand() bool {
-	err := s.ParseCommand(s.Env.OriginalCommand)
-	return err == nil
-}
-
 func (s *Shell) parseWho() {
 	for _, argument := range s.Arguments {
 		if keyId := tryParseKeyId(argument); keyId != "" {
@@ -79,26 +75,29 @@
 	}
 }
 
-func tryParseKeyId(argument string) string {
-	matchInfo := whoKeyRegex.FindStringSubmatch(argument)
+func tryParse(r *regexp.Regexp, argument string) string {
+	// sshd may execute the session for AuthorizedKeysCommand in multiple ways:
+	// 1. key-id
+	// 2. /path/to/shell -c key-id
+	args := strings.Split(argument, " ")
+	lastArg := args[len(args)-1]
+
+	matchInfo := r.FindStringSubmatch(lastArg)
 	if len(matchInfo) == 2 {
 		// The first element is the full matched string
-		// The second element is the named `keyid`
+		// The second element is the named `keyid` or `username`
 		return matchInfo[1]
 	}
 
 	return ""
 }
 
+func tryParseKeyId(argument string) string {
+	return tryParse(whoKeyRegex, argument)
+}
+
 func tryParseUsername(argument string) string {
-	matchInfo := whoUsernameRegex.FindStringSubmatch(argument)
-	if len(matchInfo) == 2 {
-		// The first element is the full matched string
-		// The second element is the named `username`
-		return matchInfo[1]
-	}
-
-	return ""
+	return tryParse(whoUsernameRegex, argument)
 }
 
 func (s *Shell) ParseCommand(commandString string) error {
diff --git a/internal/command/lfsauthenticate/lfsauthenticate.go b/internal/command/lfsauthenticate/lfsauthenticate.go
--- a/internal/command/lfsauthenticate/lfsauthenticate.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate.go
@@ -6,6 +6,8 @@
 	"encoding/json"
 	"fmt"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
@@ -58,6 +60,11 @@
 	payload, err := c.authenticate(ctx, operation, repo, accessResponse.UserId)
 	if err != nil {
 		// return nothing just like Ruby's GitlabShell#lfs_authenticate does
+		log.WithContextFields(
+			ctx,
+			log.Fields{"operation": operation, "repo": repo, "user_id": accessResponse.UserId},
+		).WithError(err).Debug("lfsauthenticate: execute: LFS authentication failed")
+
 		return nil
 	}
 
diff --git a/internal/command/lfsauthenticate/lfsauthenticate_test.go b/internal/command/lfsauthenticate/lfsauthenticate_test.go
--- a/internal/command/lfsauthenticate/lfsauthenticate_test.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -70,7 +70,7 @@
 		{
 			Path: "/api/v4/internal/lfs_authenticate",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 				require.NoError(t, err)
 
@@ -94,7 +94,7 @@
 		{
 			Path: "/api/v4/internal/allowed",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 				require.NoError(t, err)
 
diff --git a/internal/command/personalaccesstoken/personalaccesstoken.go b/internal/command/personalaccesstoken/personalaccesstoken.go
--- a/internal/command/personalaccesstoken/personalaccesstoken.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken.go
@@ -8,6 +8,8 @@
 	"strings"
 	"time"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -38,6 +40,10 @@
 		return err
 	}
 
+	log.WithContextFields(ctx, log.Fields{
+		"token_args": c.TokenArgs,
+	}).Info("personalaccesstoken: execute: requesting token")
+
 	response, err := c.getPersonalAccessToken(ctx)
 	if err != nil {
 		return err
diff --git a/internal/command/personalaccesstoken/personalaccesstoken_test.go b/internal/command/personalaccesstoken/personalaccesstoken_test.go
--- a/internal/command/personalaccesstoken/personalaccesstoken_test.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -26,7 +26,7 @@
 		{
 			Path: "/api/v4/internal/personal_access_token",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/command/shared/accessverifier/accessverifier_test.go b/internal/command/shared/accessverifier/accessverifier_test.go
--- a/internal/command/shared/accessverifier/accessverifier_test.go
+++ b/internal/command/shared/accessverifier/accessverifier_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -27,7 +27,7 @@
 		{
 			Path: "/api/v4/internal/allowed",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var requestBody *accessverifier.Request
diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -4,19 +4,17 @@
 	"bytes"
 	"context"
 	"errors"
-
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-
 	"io"
 	"net/http"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
+	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/pktline"
-
-	"gitlab.com/gitlab-org/labkit/log"
 )
 
 type Request struct {
@@ -59,12 +57,12 @@
 	request.Data.UserId = response.Who
 
 	for _, endpoint := range data.ApiEndpoints {
-		fields := log.Fields{
+		ctxlog := log.WithContextFields(ctx, log.Fields{
 			"primary_repo": data.PrimaryRepo,
 			"endpoint":     endpoint,
-		}
+		})
 
-		log.WithFields(fields).Info("Performing custom action")
+		ctxlog.Info("customaction: processApiEndpoints: Performing custom action")
 
 		response, err := c.performRequest(ctx, client, endpoint, request)
 		if err != nil {
@@ -90,6 +88,10 @@
 		} else {
 			output = c.readFromStdinNoEOF()
 		}
+		ctxlog.WithFields(log.Fields{
+			"eof_sent":    c.EOFSent,
+			"stdin_bytes": len(output),
+		}).Debug("customaction: processApiEndpoints: stdin buffered")
 
 		request.Output = output
 	}
diff --git a/internal/command/shared/customaction/customaction_test.go b/internal/command/shared/customaction/customaction_test.go
--- a/internal/command/shared/customaction/customaction_test.go
+++ b/internal/command/shared/customaction/customaction_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -23,7 +23,7 @@
 		{
 			Path: "/geo/proxy/info_refs_receive_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
@@ -39,7 +39,7 @@
 		{
 			Path: "/geo/proxy/receive_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
@@ -92,7 +92,7 @@
 		{
 			Path: "/geo/proxy/info_refs_upload_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
@@ -108,7 +108,7 @@
 		{
 			Path: "/geo/proxy/upload_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
diff --git a/internal/command/twofactorrecover/twofactorrecover.go b/internal/command/twofactorrecover/twofactorrecover.go
--- a/internal/command/twofactorrecover/twofactorrecover.go
+++ b/internal/command/twofactorrecover/twofactorrecover.go
@@ -6,6 +6,8 @@
 	"io"
 	"strings"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -21,9 +23,14 @@
 }
 
 func (c *Command) Execute(ctx context.Context) error {
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.Debug("twofactorrecover: execute: Waiting for user input")
+
 	if c.canContinue() {
+		ctxlog.Debug("twofactorrecover: execute: User chose to continue")
 		c.displayRecoveryCodes(ctx)
 	} else {
+		ctxlog.Debug("twofactorrecover: execute: User chose not to continue")
 		fmt.Fprintln(c.ReadWriter.Out, "\nNew recovery codes have *not* been generated. Existing codes will remain valid.")
 	}
 
@@ -43,9 +50,12 @@
 }
 
 func (c *Command) displayRecoveryCodes(ctx context.Context) {
+	ctxlog := log.ContextLogger(ctx)
+
 	codes, err := c.getRecoveryCodes(ctx)
 
 	if err == nil {
+		ctxlog.Debug("twofactorrecover: displayRecoveryCodes: recovery codes successfully generated")
 		messageWithCodes :=
 			"\nYour two-factor authentication recovery codes are:\n\n" +
 				strings.Join(codes, "\n") +
@@ -54,6 +64,7 @@
 				"a new device so you do not lose access to your account again.\n"
 		fmt.Fprint(c.ReadWriter.Out, messageWithCodes)
 	} else {
+		ctxlog.WithError(err).Error("twofactorrecover: displayRecoveryCodes: failed to generate recovery codes")
 		fmt.Fprintf(c.ReadWriter.Out, "\nAn error occurred while trying to generate new recovery codes.\n%v\n", err)
 	}
 }
diff --git a/internal/command/twofactorrecover/twofactorrecover_test.go b/internal/command/twofactorrecover/twofactorrecover_test.go
--- a/internal/command/twofactorrecover/twofactorrecover_test.go
+++ b/internal/command/twofactorrecover/twofactorrecover_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strings"
 	"testing"
@@ -27,7 +27,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_recovery_codes",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -5,6 +5,8 @@
 	"fmt"
 	"io"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -18,11 +20,18 @@
 }
 
 func (c *Command) Execute(ctx context.Context) error {
-	err := c.verifyOTP(ctx, c.getOTP())
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.Info("twofactorverify: execute: waiting for user input")
+	otp := c.getOTP()
+
+	ctxlog.Info("twofactorverify: execute: verifying entered OTP")
+	err := c.verifyOTP(ctx, otp)
 	if err != nil {
+		ctxlog.WithError(err).Error("twofactorverify: execute: OTP verification failed")
 		return err
 	}
 
+	ctxlog.WithError(err).Info("twofactorverify: execute: OTP verified")
 	return nil
 }
 
diff --git a/internal/command/twofactorverify/twofactorverify_test.go b/internal/command/twofactorverify/twofactorverify_test.go
--- a/internal/command/twofactorverify/twofactorverify_test.go
+++ b/internal/command/twofactorverify/twofactorverify_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -22,7 +22,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_otp_check",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -2,7 +2,6 @@
 
 import (
 	"errors"
-	"io/ioutil"
 	"net/url"
 	"os"
 	"path"
@@ -73,6 +72,7 @@
 	RootDir               string
 	LogFile               string `yaml:"log_file,omitempty"`
 	LogFormat             string `yaml:"log_format,omitempty"`
+	LogLevel              string `yaml:"log_level,omitempty"`
 	GitlabUrl             string `yaml:"gitlab_url"`
 	GitlabRelativeURLRoot string `yaml:"gitlab_relative_url_root"`
 	GitlabTracing         string `yaml:"gitlab_tracing"`
@@ -94,6 +94,7 @@
 	DefaultConfig = Config{
 		LogFile:   "gitlab-shell.log",
 		LogFormat: "json",
+		LogLevel:  "info",
 		Server:    DefaultServerConfig,
 		User:      "git",
 	}
@@ -174,7 +175,7 @@
 	*cfg = DefaultConfig
 	cfg.RootDir = filepath.Dir(path)
 
-	configBytes, err := ioutil.ReadFile(path)
+	configBytes, err := os.ReadFile(path)
 	if err != nil {
 		return nil, err
 	}
@@ -218,7 +219,7 @@
 		cfg.SecretFilePath = path.Join(cfg.RootDir, cfg.SecretFilePath)
 	}
 
-	secretFileContent, err := ioutil.ReadFile(cfg.SecretFilePath)
+	secretFileContent, err := os.ReadFile(cfg.SecretFilePath)
 	if err != nil {
 		return err
 	}
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -32,7 +32,7 @@
 	client, err := config.HttpClient()
 	require.NoError(t, err)
 
-	_, err = client.Get("http://host.com/path")
+	_, err = client.Get(url)
 	require.NoError(t, err)
 
 	ms, err := prometheus.DefaultGatherer.Gather()
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -3,8 +3,9 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
+	"os"
 	"path"
 	"testing"
 
@@ -165,14 +166,14 @@
 func setup(t *testing.T, allowedPayload string) *Client {
 	testhelper.PrepareTestRootDir(t)
 
-	body, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
+	body, err := os.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
 	require.NoError(t, err)
 
 	var bodyWithPayload []byte
 
 	if allowedPayload != "" {
 		allowedWithPayloadPath := path.Join(testhelper.TestRoot, allowedPayload)
-		bodyWithPayload, err = ioutil.ReadFile(allowedWithPayloadPath)
+		bodyWithPayload, err = os.ReadFile(allowedWithPayloadPath)
 		require.NoError(t, err)
 	}
 
@@ -180,7 +181,7 @@
 		{
 			Path: "/api/v4/internal/allowed",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var requestBody *Request
diff --git a/internal/gitlabnet/lfsauthenticate/client_test.go b/internal/gitlabnet/lfsauthenticate/client_test.go
--- a/internal/gitlabnet/lfsauthenticate/client_test.go
+++ b/internal/gitlabnet/lfsauthenticate/client_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -24,7 +24,7 @@
 		{
 			Path: "/api/v4/internal/lfs_authenticate",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 				require.NoError(t, err)
 
diff --git a/internal/gitlabnet/personalaccesstoken/client_test.go b/internal/gitlabnet/personalaccesstoken/client_test.go
--- a/internal/gitlabnet/personalaccesstoken/client_test.go
+++ b/internal/gitlabnet/personalaccesstoken/client_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -24,7 +24,7 @@
 		{
 			Path: "/api/v4/internal/personal_access_token",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/gitlabnet/twofactorrecover/client_test.go b/internal/gitlabnet/twofactorrecover/client_test.go
--- a/internal/gitlabnet/twofactorrecover/client_test.go
+++ b/internal/gitlabnet/twofactorrecover/client_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -24,7 +24,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_recovery_codes",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/gitlabnet/twofactorverify/client_test.go b/internal/gitlabnet/twofactorverify/client_test.go
--- a/internal/gitlabnet/twofactorverify/client_test.go
+++ b/internal/gitlabnet/twofactorverify/client_test.go
@@ -3,11 +3,12 @@
 import (
 	"context"
 	"encoding/json"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
@@ -20,7 +21,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_otp_check",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -9,7 +9,9 @@
 	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
 	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
 	"google.golang.org/grpc"
+	grpccodes "google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
+	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
@@ -43,12 +45,25 @@
 func (gc *GitalyCommand) RunGitalyCommand(ctx context.Context, handler GitalyHandlerFunc) error {
 	conn, err := getConn(ctx, gc)
 	if err != nil {
+		log.ContextLogger(ctx).WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Failed to get connection to execute Git command")
+
 		return err
 	}
 	defer conn.Close()
 
 	childCtx := withOutgoingMetadata(ctx, gc.Features)
-	_, err = handler(childCtx, conn)
+	ctxlog := log.ContextLogger(childCtx)
+	exitStatus, err := handler(childCtx, conn)
+
+	if err != nil {
+		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
+			ctxlog.WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Gitaly is unavailable")
+
+			return fmt.Errorf("The git server, Gitaly, is not available at this time. Please contact your administrator.")
+		}
+
+		ctxlog.WithError(err).WithFields(log.Fields{"exit_status": exitStatus}).Error("Failed to execute Git command")
+	}
 
 	return err
 }
@@ -110,7 +125,7 @@
 	if serviceName == "" {
 		serviceName = "gitlab-shell-unknown"
 
-		log.WithFields(log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
+		log.WithContextFields(ctx, log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
 	}
 
 	serviceName = fmt.Sprintf("%s-%s", serviceName, gc.ServiceName)
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -7,7 +7,9 @@
 
 	"github.com/stretchr/testify/require"
 	"google.golang.org/grpc"
+	grpccodes "google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
+	grpcstatus "google.golang.org/grpc/status"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -45,6 +47,18 @@
 	require.EqualError(t, err, "no gitaly_address given")
 }
 
+func TestUnavailableGitalyErr(t *testing.T) {
+	cmd := GitalyCommand{
+		Config:  &config.Config{},
+		Address: "tcp://localhost:9999",
+	}
+
+	expectedErr := grpcstatus.Error(grpccodes.Unavailable, "error")
+	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, expectedErr))
+
+	require.EqualError(t, err, "The git server, Gitaly, is not available at this time. Please contact your administrator.")
+}
+
 func TestRunGitalyCommandMetadata(t *testing.T) {
 	tests := []struct {
 		name string
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -3,7 +3,6 @@
 import (
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log/syslog"
 	"os"
 	"time"
@@ -36,6 +35,7 @@
 		log.WithFormatter(logFmt(cfg.LogFormat)),
 		log.WithOutputName(logFile(cfg.LogFile)),
 		log.WithTimezone(time.UTC),
+		log.WithLogLevel(cfg.LogLevel),
 	}
 }
 
@@ -43,7 +43,7 @@
 // mode an empty LogFile is not accepted and syslog is used as a fallback when LogFile could not be
 // opened for writing.
 func Configure(cfg *config.Config) io.Closer {
-	var closer io.Closer = ioutil.NopCloser(nil)
+	var closer io.Closer = io.NopCloser(nil)
 	err := fmt.Errorf("No logfile specified")
 
 	if cfg.LogFile != "" {
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -1,10 +1,8 @@
 package logger
 
 import (
-	"io/ioutil"
 	"os"
 	"regexp"
-	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -14,7 +12,7 @@
 )
 
 func TestConfigure(t *testing.T) {
-	tmpFile, err := ioutil.TempFile(os.TempDir(), "logtest-")
+	tmpFile, err := os.CreateTemp(os.TempDir(), "logtest-")
 	require.NoError(t, err)
 	defer tmpFile.Close()
 
@@ -27,16 +25,41 @@
 	defer closer.Close()
 
 	log.Info("this is a test")
+	log.WithFields(log.Fields{}).Debug("debug log message")
 
 	tmpFile.Close()
 
-	data, err := ioutil.ReadFile(tmpFile.Name())
+	data, err := os.ReadFile(tmpFile.Name())
+	require.NoError(t, err)
+	require.Contains(t, string(data), `msg":"this is a test"`)
+	require.NotContains(t, string(data), `msg:":"debug log message"`)
+}
+
+func TestConfigureWithDebugLogLevel(t *testing.T) {
+	tmpFile, err := os.CreateTemp(os.TempDir(), "logtest-")
 	require.NoError(t, err)
-	require.True(t, strings.Contains(string(data), `msg":"this is a test"`))
+	defer tmpFile.Close()
+
+	config := config.Config{
+		LogFile:   tmpFile.Name(),
+		LogFormat: "json",
+		LogLevel:  "debug",
+	}
+
+	closer := Configure(&config)
+	defer closer.Close()
+
+	log.WithFields(log.Fields{}).Debug("debug log message")
+
+	tmpFile.Close()
+
+	data, err := os.ReadFile(tmpFile.Name())
+	require.NoError(t, err)
+	require.Contains(t, string(data), `msg":"debug log message"`)
 }
 
 func TestConfigureWithPermissionError(t *testing.T) {
-	tmpPath, err := ioutil.TempDir(os.TempDir(), "logtest-")
+	tmpPath, err := os.MkdirTemp(os.TempDir(), "logtest-")
 	require.NoError(t, err)
 	defer os.RemoveAll(tmpPath)
 
@@ -52,7 +75,7 @@
 }
 
 func TestLogInUTC(t *testing.T) {
-	tmpFile, err := ioutil.TempFile(os.TempDir(), "logtest-")
+	tmpFile, err := os.CreateTemp(os.TempDir(), "logtest-")
 	require.NoError(t, err)
 	defer tmpFile.Close()
 	defer os.Remove(tmpFile.Name())
@@ -67,7 +90,7 @@
 
 	log.Info("this is a test")
 
-	data, err := ioutil.ReadFile(tmpFile.Name())
+	data, err := os.ReadFile(tmpFile.Name())
 	require.NoError(t, err)
 
 	utc := `[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z`
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -29,21 +29,26 @@
 }
 
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+
 	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
 
 	for newChannel := range chans {
+		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
+			ctxlog.Info("connection: handle: unknown channel type")
 			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
 			continue
 		}
 		if !c.concurrentSessions.TryAcquire(1) {
+			ctxlog.Info("connection: handle: too many concurrent sessions")
 			newChannel.Reject(ssh.ResourceShortage, "too many concurrent sessions")
 			metrics.SshdHitMaxSessions.Inc()
 			continue
 		}
 		channel, requests, err := newChannel.Accept()
 		if err != nil {
-			log.WithError(err).Info("could not accept channel")
+			ctxlog.WithError(err).Error("connection: handle: accepting channel failed")
 			c.concurrentSessions.Release(1)
 			continue
 		}
@@ -54,11 +59,12 @@
 			// Prevent a panic in a single session from taking out the whole server
 			defer func() {
 				if err := recover(); err != nil {
-					log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", c.remoteAddr)
+					ctxlog.WithField("recovered_error", err).Warn("panic handling session")
 				}
 			}()
 
 			handler(ctx, channel, requests)
+			ctxlog.Info("connection: handle: done")
 		}()
 	}
 }
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/server_config.go
@@ -0,0 +1,94 @@
+package sshd
+
+import (
+	"context"
+	"encoding/base64"
+	"fmt"
+	"os"
+	"strconv"
+	"time"
+
+	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
+
+	"gitlab.com/gitlab-org/labkit/log"
+)
+
+type serverConfig struct {
+	cfg                  *config.Config
+	hostKeys             []ssh.Signer
+	authorizedKeysClient *authorizedkeys.Client
+}
+
+func newServerConfig(cfg *config.Config) (*serverConfig, error) {
+	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+	}
+
+	var hostKeys []ssh.Signer
+	for _, filename := range cfg.Server.HostKeyFiles {
+		keyRaw, err := os.ReadFile(filename)
+		if err != nil {
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("Failed to read host key")
+			continue
+		}
+		key, err := ssh.ParsePrivateKey(keyRaw)
+		if err != nil {
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("Failed to parse host key")
+			continue
+		}
+
+		hostKeys = append(hostKeys, key)
+	}
+	if len(hostKeys) == 0 {
+		return nil, fmt.Errorf("No host keys could be loaded, aborting")
+	}
+
+	return &serverConfig{cfg: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
+}
+
+func (s *serverConfig) getAuthKey(ctx context.Context, user string, key ssh.PublicKey) (*authorizedkeys.Response, error) {
+	if user != s.cfg.User {
+		return nil, fmt.Errorf("unknown user")
+	}
+	if key.Type() == ssh.KeyAlgoDSA {
+		return nil, fmt.Errorf("DSA is prohibited")
+	}
+
+	ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
+	defer cancel()
+
+	res, err := s.authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
+	if err != nil {
+		return nil, err
+	}
+
+	return res, nil
+}
+
+func (s *serverConfig) get(ctx context.Context) *ssh.ServerConfig {
+	sshCfg := &ssh.ServerConfig{
+		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
+			res, err := s.getAuthKey(ctx, conn.User(), key)
+			if err != nil {
+				return nil, err
+			}
+
+			return &ssh.Permissions{
+				// Record the public key used for authentication.
+				Extensions: map[string]string{
+					"key-id": strconv.FormatInt(res.Id, 10),
+				},
+			}, nil
+		},
+	}
+
+	for _, key := range s.hostKeys {
+		sshCfg.AddHostKey(key)
+	}
+
+	return sshCfg
+}
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/server_config_test.go
@@ -0,0 +1,105 @@
+package sshd
+
+import (
+	"context"
+	"crypto/dsa"
+	"crypto/rand"
+	"crypto/rsa"
+	"path"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+)
+
+func TestNewServerConfigWithoutHosts(t *testing.T) {
+	_, err := newServerConfig(&config.Config{GitlabUrl: "http://localhost"})
+
+	require.Error(t, err)
+	require.Equal(t, "No host keys could be loaded, aborting", err.Error())
+}
+
+func TestFailedAuthorizedKeysClient(t *testing.T) {
+	_, err := newServerConfig(&config.Config{GitlabUrl: "ftp://localhost"})
+
+	require.Error(t, err)
+	require.Equal(t, "failed to initialize GitLab client: Error creating http client: unknown GitLab URL prefix", err.Error())
+}
+
+func TestFailedGetAuthKey(t *testing.T) {
+	testhelper.PrepareTestRootDir(t)
+
+	srvCfg := config.ServerConfig{
+		Listen:                  "127.0.0.1",
+		ConcurrentSessionsLimit: 1,
+		HostKeyFiles: []string{
+			path.Join(testhelper.TestRoot, "certs/valid/server.key"),
+			path.Join(testhelper.TestRoot, "certs/invalid-path.key"),
+			path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+		},
+	}
+
+	cfg, err := newServerConfig(
+		&config.Config{GitlabUrl: "http://localhost", User: "user", Server: srvCfg},
+	)
+	require.NoError(t, err)
+
+	testCases := []struct {
+		desc          string
+		user          string
+		key           ssh.PublicKey
+		expectedError string
+	}{
+		{
+			desc:          "wrong user",
+			user:          "wrong-user",
+			key:           rsaPublicKey(t),
+			expectedError: "unknown user",
+		}, {
+			desc:          "prohibited dsa key",
+			user:          "user",
+			key:           dsaPublicKey(t),
+			expectedError: "DSA is prohibited",
+		}, {
+			desc:          "API error",
+			user:          "user",
+			key:           rsaPublicKey(t),
+			expectedError: "Internal API unreachable",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err = cfg.getAuthKey(context.Background(), tc.user, tc.key)
+			require.Error(t, err)
+			require.Equal(t, tc.expectedError, err.Error())
+		})
+	}
+}
+
+func rsaPublicKey(t *testing.T) ssh.PublicKey {
+	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	require.NoError(t, err)
+
+	publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
+	require.NoError(t, err)
+
+	return publicKey
+}
+
+func dsaPublicKey(t *testing.T) ssh.PublicKey {
+	privateKey := new(dsa.PrivateKey)
+	params := new(dsa.Parameters)
+	require.NoError(t, dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160))
+
+	privateKey.PublicKey.Parameters = *params
+	require.NoError(t, dsa.GenerateKey(privateKey, rand.Reader))
+
+	publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
+	require.NoError(t, err)
+
+	return publicKey
+}
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -2,13 +2,16 @@
 
 import (
 	"context"
+	"errors"
 	"fmt"
+	"reflect"
 
+	"gitlab.com/gitlab-org/labkit/log"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
@@ -39,16 +42,27 @@
 }
 
 func (s *session) handle(ctx context.Context, requests <-chan *ssh.Request) {
+	ctxlog := log.ContextLogger(ctx)
+
+	ctxlog.Debug("session: handle: entering request loop")
+
 	for req := range requests {
+		sessionLog := ctxlog.WithFields(log.Fields{
+			"bytesize":   len(req.Payload),
+			"type":       req.Type,
+			"want_reply": req.WantReply,
+		})
+		sessionLog.Debug("session: handle: request received")
+
 		var shouldContinue bool
 		switch req.Type {
 		case "env":
-			shouldContinue = s.handleEnv(req)
+			shouldContinue = s.handleEnv(ctx, req)
 		case "exec":
 			shouldContinue = s.handleExec(ctx, req)
 		case "shell":
 			shouldContinue = false
-			s.exit(s.handleShell(ctx, req))
+			s.exit(ctx, s.handleShell(ctx, req))
 		default:
 			// Ignore unknown requests but don't terminate the session
 			shouldContinue = true
@@ -57,18 +71,23 @@
 			}
 		}
 
+		sessionLog.WithField("should_continue", shouldContinue).Debug("session: handle: request processed")
+
 		if !shouldContinue {
 			s.channel.Close()
 			break
 		}
 	}
+
+	ctxlog.Debug("session: handle: exiting request loop")
 }
 
-func (s *session) handleEnv(req *ssh.Request) bool {
+func (s *session) handleEnv(ctx context.Context, req *ssh.Request) bool {
 	var accepted bool
 	var envRequest envRequest
 
 	if err := ssh.Unmarshal(req.Payload, &envRequest); err != nil {
+		log.ContextLogger(ctx).WithError(err).Error("session: handleEnv: failed to unmarshal request")
 		return false
 	}
 
@@ -84,6 +103,10 @@
 		req.Reply(accepted, []byte{})
 	}
 
+	log.WithContextFields(
+		ctx, log.Fields{"accepted": accepted, "env_request": envRequest},
+	).Debug("session: handleEnv: processed")
+
 	return true
 }
 
@@ -95,7 +118,8 @@
 
 	s.execCmd = execRequest.Command
 
-	s.exit(s.handleShell(ctx, req))
+	s.exit(ctx, s.handleShell(ctx, req))
+
 	return false
 }
 
@@ -104,19 +128,11 @@
 		req.Reply(true, []byte{})
 	}
 
-	args := &commandargs.Shell{
-		GitlabKeyId: s.gitlabKeyId,
-		Env: sshenv.Env{
-			IsSSHConnection:    true,
-			OriginalCommand:    s.execCmd,
-			GitProtocolVersion: s.gitProtocolVersion,
-			RemoteAddr:         s.remoteAddr,
-		},
-	}
-
-	if err := args.ParseCommand(s.execCmd); err != nil {
-		s.toStderr("Failed to parse command: %v\n", err.Error())
-		return 128
+	env := sshenv.Env{
+		IsSSHConnection:    true,
+		OriginalCommand:    s.execCmd,
+		GitProtocolVersion: s.gitProtocolVersion,
+		RemoteAddr:         s.remoteAddr,
 	}
 
 	rw := &readwriter.ReadWriter{
@@ -125,25 +141,37 @@
 		ErrOut: s.channel.Stderr(),
 	}
 
-	cmd := command.BuildShellCommand(args, s.cfg, rw)
-	if cmd == nil {
-		s.toStderr("Unknown command: %v\n", args.CommandType)
+	cmd, err := shellCmd.NewWithKey(s.gitlabKeyId, env, s.cfg, rw)
+	if err != nil {
+		if !errors.Is(err, disallowedcommand.Error) {
+			s.toStderr(ctx, "Failed to parse command: %v\n", err.Error())
+		}
+		s.toStderr(ctx, "Unknown command: %v\n", s.execCmd)
 		return 128
 	}
 
+	cmdName := reflect.TypeOf(cmd).String()
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("session: handleShell: executing command")
+
 	if err := cmd.Execute(ctx); err != nil {
-		s.toStderr("remote: ERROR: %v\n", err.Error())
+		s.toStderr(ctx, "remote: ERROR: %v\n", err.Error())
 		return 1
 	}
 
+	ctxlog.Info("session: handleShell: command executed successfully")
+
 	return 0
 }
 
-func (s *session) toStderr(format string, args ...interface{}) {
-	fmt.Fprintf(s.channel.Stderr(), format, args...)
+func (s *session) toStderr(ctx context.Context, format string, args ...interface{}) {
+	out := fmt.Sprintf(format, args...)
+	log.WithContextFields(ctx, log.Fields{"stderr": out}).Debug("session: toStderr: output")
+	fmt.Fprint(s.channel.Stderr(), out)
 }
 
-func (s *session) exit(status uint32) {
+func (s *session) exit(ctx context.Context, status uint32) {
+	log.WithContextFields(ctx, log.Fields{"exit_status": status}).Info("session: exit: exiting")
 	req := exitStatusReq{ExitStatus: status}
 
 	s.channel.CloseWrite()
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/session_test.go
@@ -0,0 +1,189 @@
+package sshd
+
+import (
+	"bytes"
+	"context"
+	"io"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+)
+
+type fakeChannel struct {
+	stdErr             io.ReadWriter
+	sentRequestName    string
+	sentRequestPayload []byte
+}
+
+func (f *fakeChannel) Read(data []byte) (int, error) {
+	return 0, nil
+}
+
+func (f *fakeChannel) Write(data []byte) (int, error) {
+	return 0, nil
+}
+
+func (f *fakeChannel) Close() error {
+	return nil
+}
+
+func (f *fakeChannel) CloseWrite() error {
+	return nil
+}
+
+func (f *fakeChannel) SendRequest(name string, wantReply bool, payload []byte) (bool, error) {
+	f.sentRequestName = name
+	f.sentRequestPayload = payload
+
+	return true, nil
+}
+
+func (f *fakeChannel) Stderr() io.ReadWriter {
+	return f.stdErr
+}
+
+var requests = []testserver.TestRequestHandler{
+	{
+		Path: "/api/v4/internal/discover",
+		Handler: func(w http.ResponseWriter, r *http.Request) {
+			w.Write([]byte(`{"id": 1000, "name": "Test User", "username": "test-user"}`))
+		},
+	},
+}
+
+func TestHandleEnv(t *testing.T) {
+	testCases := []struct {
+		desc                    string
+		payload                 []byte
+		expectedProtocolVersion string
+		expectedResult          bool
+	}{
+		{
+			desc:                    "invalid payload",
+			payload:                 []byte("invalid"),
+			expectedProtocolVersion: "1",
+			expectedResult:          false,
+		}, {
+			desc:                    "valid payload",
+			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL", Value: "2"}),
+			expectedProtocolVersion: "2",
+			expectedResult:          true,
+		}, {
+			desc:                    "valid payload with forbidden env var",
+			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL_ENV", Value: "2"}),
+			expectedProtocolVersion: "1",
+			expectedResult:          true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			s := &session{gitProtocolVersion: "1"}
+			r := &ssh.Request{Payload: tc.payload}
+
+			require.Equal(t, s.handleEnv(context.Background(), r), tc.expectedResult)
+			require.Equal(t, s.gitProtocolVersion, tc.expectedProtocolVersion)
+		})
+	}
+}
+
+func TestHandleExec(t *testing.T) {
+	testCases := []struct {
+		desc               string
+		payload            []byte
+		expectedExecCmd    string
+		sentRequestName    string
+		sentRequestPayload []byte
+	}{
+		{
+			desc:            "invalid payload",
+			payload:         []byte("invalid"),
+			expectedExecCmd: "",
+			sentRequestName: "",
+		}, {
+			desc:               "valid payload",
+			payload:            ssh.Marshal(execRequest{Command: "discover"}),
+			expectedExecCmd:    "discover",
+			sentRequestName:    "exit-status",
+			sentRequestPayload: ssh.Marshal(exitStatusReq{ExitStatus: 0}),
+		},
+	}
+
+	url := testserver.StartHttpServer(t, requests)
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			out := &bytes.Buffer{}
+			f := &fakeChannel{stdErr: out}
+			s := &session{
+				gitlabKeyId: "root",
+				channel:     f,
+				cfg:         &config.Config{GitlabUrl: url},
+			}
+			r := &ssh.Request{Payload: tc.payload}
+
+			require.Equal(t, false, s.handleExec(context.Background(), r))
+			require.Equal(t, tc.sentRequestName, f.sentRequestName)
+			require.Equal(t, tc.sentRequestPayload, f.sentRequestPayload)
+		})
+	}
+}
+
+func TestHandleShell(t *testing.T) {
+	testCases := []struct {
+		desc             string
+		cmd              string
+		errMsg           string
+		gitlabKeyId      string
+		expectedExitCode uint32
+	}{
+		{
+			desc:             "fails to parse command",
+			cmd:              `\`,
+			errMsg:           "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
+			gitlabKeyId:      "root",
+			expectedExitCode: 128,
+		}, {
+			desc:             "specified command is unknown",
+			cmd:              "unknown-command",
+			errMsg:           "Unknown command: unknown-command\n",
+			gitlabKeyId:      "root",
+			expectedExitCode: 128,
+		}, {
+			desc:             "fails to parse command",
+			cmd:              "discover",
+			gitlabKeyId:      "",
+			errMsg:           "remote: ERROR: Failed to get username: who='' is invalid\n",
+			expectedExitCode: 1,
+		}, {
+			desc:             "fails to parse command",
+			cmd:              "discover",
+			errMsg:           "",
+			gitlabKeyId:      "root",
+			expectedExitCode: 0,
+		},
+	}
+
+	url := testserver.StartHttpServer(t, requests)
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			out := &bytes.Buffer{}
+			s := &session{
+				gitlabKeyId: tc.gitlabKeyId,
+				execCmd:     tc.cmd,
+				channel:     &fakeChannel{stdErr: out},
+				cfg:         &config.Config{GitlabUrl: url},
+			}
+			r := &ssh.Request{}
+
+			require.Equal(t, tc.expectedExitCode, s.handleShell(context.Background(), r))
+			require.Equal(t, tc.errMsg, out.String())
+		})
+	}
+}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -2,13 +2,9 @@
 
 import (
 	"context"
-	"encoding/base64"
-	"errors"
 	"fmt"
-	"io/ioutil"
 	"net"
 	"net/http"
-	"strconv"
 	"sync"
 	"time"
 
@@ -16,7 +12,6 @@
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/log"
@@ -35,44 +30,24 @@
 type Server struct {
 	Config *config.Config
 
-	status               status
-	statusMu             sync.Mutex
-	wg                   sync.WaitGroup
-	listener             net.Listener
-	hostKeys             []ssh.Signer
-	authorizedKeysClient *authorizedkeys.Client
+	status       status
+	statusMu     sync.Mutex
+	wg           sync.WaitGroup
+	listener     net.Listener
+	serverConfig *serverConfig
 }
 
 func NewServer(cfg *config.Config) (*Server, error) {
-	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
+	serverConfig, err := newServerConfig(cfg)
 	if err != nil {
-		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+		return nil, err
 	}
 
-	var hostKeys []ssh.Signer
-	for _, filename := range cfg.Server.HostKeyFiles {
-		keyRaw, err := ioutil.ReadFile(filename)
-		if err != nil {
-			log.WithError(err).Warnf("Failed to read host key %v", filename)
-			continue
-		}
-		key, err := ssh.ParsePrivateKey(keyRaw)
-		if err != nil {
-			log.WithError(err).Warnf("Failed to parse host key %v", filename)
-			continue
-		}
-
-		hostKeys = append(hostKeys, key)
-	}
-	if len(hostKeys) == 0 {
-		return nil, fmt.Errorf("No host keys could be loaded, aborting")
-	}
-
-	return &Server{Config: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
+	return &Server{Config: cfg, serverConfig: serverConfig}, nil
 }
 
 func (s *Server) ListenAndServe(ctx context.Context) error {
-	if err := s.listen(); err != nil {
+	if err := s.listen(ctx); err != nil {
 		return err
 	}
 	defer s.listener.Close()
@@ -110,7 +85,7 @@
 	return mux
 }
 
-func (s *Server) listen() error {
+func (s *Server) listen(ctx context.Context) error {
 	sshListener, err := net.Listen("tcp", s.Config.Server.Listen)
 	if err != nil {
 		return fmt.Errorf("failed to listen for connection: %w", err)
@@ -119,13 +94,14 @@
 	if s.Config.Server.ProxyProtocol {
 		sshListener = &proxyproto.Listener{
 			Listener:          sshListener,
+			Policy:            unconditionalRequirePolicy,
 			ReadHeaderTimeout: ProxyHeaderTimeout,
 		}
 
-		log.Info("Proxy protocol is enabled")
+		log.ContextLogger(ctx).Info("Proxy protocol is enabled")
 	}
 
-	log.WithFields(log.Fields{"tcp_address": sshListener.Addr().String()}).Info("Listening for SSH connections")
+	log.WithContextFields(ctx, log.Fields{"tcp_address": sshListener.Addr().String()}).Info("Listening for SSH connections")
 
 	s.listener = sshListener
 
@@ -142,7 +118,7 @@
 				break
 			}
 
-			log.WithError(err).Warn("Failed to accept connection")
+			log.ContextLogger(ctx).WithError(err).Warn("Failed to accept connection")
 			continue
 		}
 
@@ -168,57 +144,29 @@
 	return s.status
 }
 
-func (s *Server) serverConfig(ctx context.Context) *ssh.ServerConfig {
-	sshCfg := &ssh.ServerConfig{
-		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
-			if conn.User() != s.Config.User {
-				return nil, errors.New("unknown user")
-			}
-			if key.Type() == ssh.KeyAlgoDSA {
-				return nil, errors.New("DSA is prohibited")
-			}
-			ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
-			defer cancel()
-			res, err := s.authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
-			if err != nil {
-				return nil, err
-			}
-
-			return &ssh.Permissions{
-				// Record the public key used for authentication.
-				Extensions: map[string]string{
-					"key-id": strconv.FormatInt(res.Id, 10),
-				},
-			}, nil
-		},
-	}
-
-	for _, key := range s.hostKeys {
-		sshCfg.AddHostKey(key)
-	}
-
-	return sshCfg
-}
-
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
 	remoteAddr := nconn.RemoteAddr().String()
 
 	defer s.wg.Done()
 	defer nconn.Close()
 
+	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
+	defer cancel()
+
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": remoteAddr})
+
 	// Prevent a panic in a single connection from taking out the whole server
 	defer func() {
 		if err := recover(); err != nil {
-			log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", remoteAddr)
+			ctxlog.Warn("panic handling session")
 		}
 	}()
 
-	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
-	defer cancel()
+	ctxlog.Info("server: handleConn: start")
 
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig(ctx))
+	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
-		log.WithError(err).Info("Failed to initialize SSH connection")
+		ctxlog.WithError(err).Error("server: handleConn: failed to initialize SSH connection")
 		return
 	}
 
@@ -235,4 +183,10 @@
 
 		session.handle(ctx, requests)
 	})
+
+	ctxlog.Info("server: handleConn: done")
 }
+
+func unconditionalRequirePolicy(_ net.Addr) (proxyproto.Policy, error) {
+	return proxyproto.REQUIRE, nil
+}
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -3,9 +3,9 @@
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
+	"os"
 	"path"
 	"testing"
 	"time"
@@ -47,6 +47,18 @@
 	verifyStatus(t, s, StatusClosed)
 }
 
+func TestListenAndServeRejectsPlainConnectionsWhenProxyProtocolEnabled(t *testing.T) {
+	setupServerWithProxyProtocolEnabled(t)
+
+	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
+	if client != nil {
+		client.Close()
+	}
+
+	require.Error(t, err, "Expected plain SSH request to be failed")
+	require.Regexp(t, "ssh: handshake failed", err.Error())
+}
+
 func TestCorrelationId(t *testing.T) {
 	setupServer(t)
 
@@ -104,9 +116,39 @@
 	require.Equal(t, 200, r.Result().StatusCode)
 }
 
+func TestInvalidClientConfig(t *testing.T) {
+	setupServer(t)
+
+	cfg := clientConfig(t)
+	cfg.User = "unknown"
+	_, err := ssh.Dial("tcp", serverUrl, cfg)
+	require.Error(t, err)
+}
+
+func TestInvalidServerConfig(t *testing.T) {
+	s := &Server{Config: &config.Config{Server: config.ServerConfig{Listen: "invalid"}}}
+	err := s.ListenAndServe(context.Background())
+
+	require.Error(t, err)
+	require.Equal(t, "failed to listen for connection: listen tcp: address invalid: missing port in address", err.Error())
+	require.Nil(t, s.Shutdown())
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
+	return setupServerWithConfig(t, nil)
+}
+
+func setupServerWithProxyProtocolEnabled(t *testing.T) *Server {
+	t.Helper()
+
+	return setupServerWithConfig(t, &config.Config{Server: config.ServerConfig{ProxyProtocol: true}})
+}
+
+func setupServerWithConfig(t *testing.T, cfg *config.Config) *Server {
+	t.Helper()
+
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/authorized_keys",
@@ -130,13 +172,20 @@
 	testhelper.PrepareTestRootDir(t)
 
 	url := testserver.StartSocketHttpServer(t, requests)
-	srvCfg := config.ServerConfig{
-		Listen:                  serverUrl,
-		ConcurrentSessionsLimit: 1,
-		HostKeyFiles:            []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")},
+
+	if cfg == nil {
+		cfg = &config.Config{}
 	}
 
-	s, err := NewServer(&config.Config{User: user, RootDir: "/tmp", GitlabUrl: url, Server: srvCfg})
+	// All things that don't need to be configurable in tests yet
+	cfg.GitlabUrl = url
+	cfg.RootDir = "/tmp"
+	cfg.User = user
+	cfg.Server.Listen = serverUrl
+	cfg.Server.ConcurrentSessionsLimit = 1
+	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
+
+	s, err := NewServer(cfg)
 	require.NoError(t, err)
 
 	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
@@ -148,11 +197,11 @@
 }
 
 func clientConfig(t *testing.T) *ssh.ClientConfig {
-	keyRaw, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server_authorized_key"))
+	keyRaw, err := os.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server_authorized_key"))
 	pKey, _, _, _, err := ssh.ParseAuthorizedKey(keyRaw)
 	require.NoError(t, err)
 
-	key, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/client/key.pem"))
+	key, err := os.ReadFile(path.Join(testhelper.TestRoot, "certs/client/key.pem"))
 	require.NoError(t, err)
 	signer, err := ssh.ParsePrivateKey(key)
 	require.NoError(t, err)
@@ -177,14 +226,5 @@
 }
 
 func verifyStatus(t *testing.T, s *Server, st status) {
-	for i := 5; i < 500; i += 50 {
-		if s.getStatus() == st {
-			break
-		}
-
-		// Sleep incrementally ~2s in total
-		time.Sleep(time.Duration(i) * time.Millisecond)
-	}
-
-	require.Equal(t, st, s.getStatus())
+	require.Eventually(t, func() bool { return s.getStatus() == st }, 2*time.Second, time.Millisecond)
 }
diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go
--- a/internal/testhelper/testhelper.go
+++ b/internal/testhelper/testhelper.go
@@ -2,7 +2,6 @@
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path"
 	"runtime"
@@ -13,7 +12,7 @@
 )
 
 var (
-	TestRoot, _ = ioutil.TempDir("", "test-gitlab-shell")
+	TestRoot, _ = os.MkdirTemp("", "test-gitlab-shell")
 )
 
 func TempEnv(env map[string]string) func() {
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1640273261 -3600
#      Thu Dec 23 16:27:41 2021 +0100
# Branch heptapod
# Node ID dd834f017fa24a407df330ffe8b37fae0d65c2a8
# Parent  500ed5c3c7f5402868765b8f4e8794a84df9b0e4
Heptapod CI: updated Gitaly service version

In 6e4e76df7ef9, upstream stops accidentally using the `latest` version,
which has stayed at 13.3, but GitLab Shell acceptance tests don't pass
with `master` version of the Gitaly image (actual latest development
version). They do with the one that matches the GitLab version for
GitLab Shell 13.22.1

diff --git a/heptapod-ci.yml b/heptapod-ci.yml
--- a/heptapod-ci.yml
+++ b/heptapod-ci.yml
@@ -31,7 +31,7 @@
     - go version
     - which go
   services:
-    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:latest
+    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:v14.5.2
       # Disable the hooks so we don't have to stub the GitLab API
       command: ["bash", "-c", "mkdir -p /home/git/repositories && rm -rf /srv/gitlab-shell/hooks/* && exec /usr/bin/env GITALY_TESTING_NO_GIT_HOOKS=1 /scripts/process-wrapper"]
       alias: gitaly
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1640272345 -3600
#      Thu Dec 23 16:12:25 2021 +0100
# Branch heptapod
# Node ID 7f259a883e58759e4734cb0f38449379bb36aca6
# Parent  dd834f017fa24a407df330ffe8b37fae0d65c2a8
Updated Heptapod version files for release

Part of heptapod#596

diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -9,6 +9,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 13.22.0
+
+- Jump to upstream GitLab Shell v13.22.1 (GitLab 14.5 series)
+
 ## 13.21.0
 
 - Jump to upstream GitLab Shell v13.21.1 (GitLab 14.4 series)
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-13.21.0
+13.22.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1640540036 -3600
#      Sun Dec 26 18:33:56 2021 +0100
# Branch heptapod
# Node ID c13639d6d9742fc361e7df2acee2661f37f16cd8
# Parent  7f259a883e58759e4734cb0f38449379bb36aca6
Added tag heptapod-13.22.0 for changeset 7f259a883e58

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -36,3 +36,4 @@
 b84ce63181d5c0d07585be867dbe957eb6d35348 heptapod-13.19.1
 f593b81abfe52ca2083440f3f470dce3d13079da heptapod-13.19.2
 b15301c706769652286af112a4437dc8cdfac86e heptapod-13.21.0
+7f259a883e58759e4734cb0f38449379bb36aca6 heptapod-13.22.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1642959359 -3600
#      Sun Jan 23 18:35:59 2022 +0100
# Branch heptapod-stable
# Node ID eed45a48a4bd15aa1f57138e286080b4aaddec37
# Parent  7a9511f85a6a3aca688269a37504d371068a2961
# Parent  c13639d6d9742fc361e7df2acee2661f37f16cd8
heptapod#612: Making Heptapod 0.28 the new stable

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -45,7 +45,7 @@
     - go version
     - which go
   services:
-    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:latest
+    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:master
       # Disable the hooks so we don't have to stub the GitLab API
       command: ["bash", "-c", "mkdir -p /home/git/repositories && rm -rf /srv/gitlab-shell/hooks/* && exec /usr/bin/env GITALY_TESTING_NO_GIT_HOOKS=1 /scripts/process-wrapper"]
       alias: gitaly
diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -36,3 +36,4 @@
 b84ce63181d5c0d07585be867dbe957eb6d35348 heptapod-13.19.1
 f593b81abfe52ca2083440f3f470dce3d13079da heptapod-13.19.2
 b15301c706769652286af112a4437dc8cdfac86e heptapod-13.21.0
+7f259a883e58759e4734cb0f38449379bb36aca6 heptapod-13.22.0
diff --git a/.ruby-version b/.ruby-version
--- a/.ruby-version
+++ b/.ruby-version
@@ -1,1 +1,1 @@
-2.7.2
+2.7.4
diff --git a/.tool-versions b/.tool-versions
new file mode 100644
--- /dev/null
+++ b/.tool-versions
@@ -0,0 +1,2 @@
+ruby 2.7.4
+golang 1.16.10
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,9 +1,40 @@
+v13.22.1
+
+- Remove SSL_CERT_DIR logging !546
+
+v13.22.0
+
+- Relax key and username matching for sshd !540
+- Add logging to handler/exec.go and config/config.go !539
+- Improve logging for non-git commands !538
+- Update to Go v1.16.9 !537
+- Reject non-proxied connections when proxy protocol is enabled !536
+- Log command invocation !535
+- Fix logging channel type !534
+- Resolve an error-swallowing issue !533
+- Add more logging to gitlab-sshd !531
+- Respect log-level configuration again !530
+- Improve err message given when Gitaly unavailable !526
+- makefile: properly escape '$' in VERSION_STRING !525
+- Add context fields to logging !524
+- Extract server config related code out of sshd.go !523
+- Add TestInvalidClientConfig and TestNewServerWithoutHosts for sshd.go !518
+- Update Ruby version to 2.7.4 and add Go version 1.16.8 for tooling !517
+
 v13.21.1
 
 - Only validate SSL cert file exists if a value is supplied !527
 
 v13.21.0
 
+- Switch to labkit for logging system setup !504
+- Remove some unreliable tests !503
+- Make gofmt check fail if there are any matching files !500
+- Update go-proxyproto to v0.6.0 !499
+- Switch to labkit/log for logging functionality !498
+- Unit tests for internal/sshd/connection.go !497
+- Prometheus metrics for HTTP requests !496
+- Refactor testhelper.PrepareTestRootDir using t.Cleanup !493
 - Change default logging format to JSON !476
 - Shutdown sshd gracefully !484
 - Provide liveness and readiness probes !494
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -9,6 +9,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 13.22.0
+
+- Jump to upstream GitLab Shell v13.22.1 (GitLab 14.5 series)
+
 ## 13.21.0
 
 - Jump to upstream GitLab Shell v13.21.1 (GitLab 14.4 series)
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-13.21.0
+13.22.0
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -2,12 +2,13 @@
 
 GO_SOURCES := $(shell find . -name '*.go')
 VERSION_STRING := $(shell ./compute_version || echo unknown)
-BUILD_TIME := $(shell date -u +%Y%m%d.%H%M%S)
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)"
 
 PREFIX ?= /usr/local
 
+build: bin/gitlab-shell
+
 validate: verify test
 
 verify: verify_golang
@@ -40,7 +41,6 @@
 _script_install:
 	bin/install
 
-build: bin/gitlab-shell
 compile: bin/gitlab-shell
 bin/gitlab-shell: $(GO_SOURCES)
 	GOBIN="$(CURDIR)/bin" go install $(GOBUILD_FLAGS) ./cmd/...
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -102,6 +102,21 @@
 
 Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
 
+## Logging Guidelines
+
+In general, it should be possible to determine the structure, but not content,
+of a gitlab-shell or gitlab-sshd session just from inspecting the logs. Some
+guidelines:
+
+- We use [`gitlab.com/gitlab-org/labkit/log`](https://pkg.go.dev/gitlab.com/gitlab-org/labkit/log)
+  for logging functionality
+- **Always** include a correlation ID
+- Log messages should be invariant and unique. Include accessory information in
+  fields, using `log.WithField`, `log.WithFields`, or `log.WithError`.
+- Log success cases as well as error cases
+- Logging too much is better than not logging enough. If a message seems too
+  verbose, consider reducing the log level before removing the message.
+
 ## Releasing
 
 See [PROCESS.md](./PROCESS.md)
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.21.1
+13.22.1
diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -5,7 +5,7 @@
 	"encoding/base64"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"path"
 	"strings"
@@ -83,7 +83,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, string(responseBody), "Hello")
 	})
@@ -99,7 +99,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 		require.Equal(t, "Echo: {\"key\":\"value\"}", string(responseBody))
 	})
@@ -155,7 +155,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
@@ -170,7 +170,7 @@
 
 		defer response.Body.Close()
 
-		responseBody, err := ioutil.ReadAll(response.Body)
+		responseBody, err := io.ReadAll(response.Body)
 		require.NoError(t, err)
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
@@ -194,7 +194,7 @@
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				require.Equal(t, http.MethodPost, r.Method)
 
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -6,7 +6,6 @@
 	"crypto/x509"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"os"
@@ -153,7 +152,7 @@
 	}
 
 	if hcc.caPath != "" {
-		fis, _ := ioutil.ReadDir(hcc.caPath)
+		fis, _ := os.ReadDir(hcc.caPath)
 		for _, fi := range fis {
 			if fi.IsDir() {
 				continue
@@ -174,7 +173,6 @@
 			return nil, "", err
 		}
 		tlsConfig.Certificates = []tls.Certificate{cert}
-		tlsConfig.BuildNameToCertificate()
 	}
 
 	transport := &http.Transport{
@@ -185,7 +183,7 @@
 }
 
 func addCertToPool(certPool *x509.CertPool, fileName string) {
-	cert, err := ioutil.ReadFile(fileName)
+	cert, err := os.ReadFile(fileName)
 	if err == nil {
 		certPool.AppendCertsFromPEM(cert)
 	}
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -4,7 +4,7 @@
 	"context"
 	"encoding/base64"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strings"
 	"testing"
@@ -64,7 +64,7 @@
 	defer response.Body.Close()
 
 	require.NotNil(t, response)
-	responseBody, err := ioutil.ReadAll(response.Body)
+	responseBody, err := io.ReadAll(response.Body)
 	require.NoError(t, err)
 
 	headerParts := strings.Split(string(responseBody), " ")
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"path"
 	"testing"
@@ -57,7 +57,7 @@
 
 			defer response.Body.Close()
 
-			responseBody, err := ioutil.ReadAll(response.Body)
+			responseBody, err := io.ReadAll(response.Body)
 			require.NoError(t, err)
 			require.Equal(t, string(responseBody), "Hello")
 		})
diff --git a/client/testserver/gitalyserver.go b/client/testserver/gitalyserver.go
--- a/client/testserver/gitalyserver.go
+++ b/client/testserver/gitalyserver.go
@@ -1,7 +1,6 @@
 package testserver
 
 import (
-	"io/ioutil"
 	"net"
 	"os"
 	"path"
@@ -61,7 +60,7 @@
 func StartGitalyServer(t *testing.T) (string, *TestGitalyServer) {
 	t.Helper()
 
-	tempDir, _ := ioutil.TempDir("", "gitlab-shell-test-api")
+	tempDir, _ := os.MkdirTemp("", "gitlab-shell-test-api")
 	gitalySocketPath := path.Join(tempDir, "gitaly.sock")
 	t.Cleanup(func() { os.RemoveAll(tempDir) })
 
diff --git a/client/testserver/testserver.go b/client/testserver/testserver.go
--- a/client/testserver/testserver.go
+++ b/client/testserver/testserver.go
@@ -3,7 +3,7 @@
 import (
 	"crypto/tls"
 	"crypto/x509"
-	"io/ioutil"
+	"io"
 	"log"
 	"net"
 	"net/http"
@@ -18,7 +18,7 @@
 )
 
 var (
-	tempDir, _ = ioutil.TempDir("", "gitlab-shell-test-api")
+	tempDir, _ = os.MkdirTemp("", "gitlab-shell-test-api")
 	testSocket = path.Join(tempDir, "internal.sock")
 )
 
@@ -41,7 +41,7 @@
 		Handler: buildHandler(handlers),
 		// We'll put this server through some nasty stuff we don't want
 		// in our test output
-		ErrorLog: log.New(ioutil.Discard, "", 0),
+		ErrorLog: log.New(io.Discard, "", 0),
 	}
 	go server.Serve(socketListener)
 
@@ -73,10 +73,9 @@
 		Certificates: []tls.Certificate{cer},
 		MinVersion:   tls.VersionTLS12,
 	}
-	server.TLS.BuildNameToCertificate()
 
 	if clientCAPath != "" {
-		caCert, err := ioutil.ReadFile(clientCAPath)
+		caCert, err := os.ReadFile(clientCAPath)
 		require.NoError(t, err)
 
 		caCertPool := x509.NewCertPool()
diff --git a/cmd/check/command/command.go b/cmd/check/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/check/command/command.go
@@ -0,0 +1,21 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+)
+
+func New(config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	if cmd := build(config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func build(config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	return &healthcheck.Command{Config: config, ReadWriter: readWriter}
+}
diff --git a/cmd/check/command/command_test.go b/cmd/check/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/check/command/command_test.go
@@ -0,0 +1,42 @@
+package command_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	basicConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a Healthcheck command",
+			config:       basicConfig,
+			expectedType: &healthcheck.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := command.New(tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
diff --git a/cmd/check/main.go b/cmd/check/main.go
--- a/cmd/check/main.go
+++ b/cmd/check/main.go
@@ -4,12 +4,12 @@
 	"fmt"
 	"os"
 
+	checkCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
 func main() {
@@ -34,7 +34,7 @@
 	logCloser := logger.Configure(config)
 	defer logCloser.Close()
 
-	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
+	cmd, err := checkCmd.New(config, readWriter)
 	if err != nil {
 		fmt.Fprintf(readWriter.ErrOut, "%v\n", err)
 		os.Exit(1)
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command.go b/cmd/gitlab-shell-authorized-keys-check/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command.go
@@ -0,0 +1,37 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+)
+
+func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(arguments)
+	if err != nil {
+		return nil, err
+	}
+
+	if cmd := build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func Parse(arguments []string) (*commandargs.AuthorizedKeys, error) {
+	args := &commandargs.AuthorizedKeys{Arguments: arguments}
+
+	if err := args.Parse(); err != nil {
+		return nil, err
+	}
+
+	return args, nil
+}
+
+func build(args *commandargs.AuthorizedKeys, config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	return &authorizedkeys.Command{Config: config, Args: args, ReadWriter: readWriter}
+}
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command_test.go b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
@@ -0,0 +1,120 @@
+package command_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	authorizedKeysExec = &executable.Executable{Name: executable.AuthorizedKeysCheck}
+	basicConfig        = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a AuthorizedKeys command",
+			executable:   authorizedKeysExec,
+			arguments:    []string{"git", "git", "key"},
+			config:       basicConfig,
+			expectedType: &authorizedkeys.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := command.New(tc.arguments, tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
+
+func TestParseSuccess(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		expectedArgs commandargs.CommandArgs
+		expectError  bool
+	}{
+		{
+			desc:         "It parses authorized-keys command",
+			executable:   &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:    []string{"git", "git", "key"},
+			expectedArgs: &commandargs.AuthorizedKeys{Arguments: []string{"git", "git", "key"}, ExpectedUser: "git", ActualUser: "git", Key: "key"},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := command.Parse(tc.arguments)
+
+			if !tc.expectError {
+				require.NoError(t, err)
+				require.Equal(t, tc.expectedArgs, result)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
+
+func TestParseFailure(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		arguments     []string
+		expectedError string
+	}{
+		{
+			desc:          "With not enough arguments for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user"},
+			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
+		},
+		{
+			desc:          "With too many arguments for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user", "user", "key", "something-else"},
+			expectedError: "# Insufficient arguments. 4. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
+		},
+		{
+			desc:          "With missing username for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user", "", "key"},
+			expectedError: "# No username provided",
+		},
+		{
+			desc:          "With missing key for the AuthorizedKeysCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
+			arguments:     []string{"user", "user", ""},
+			expectedError: "# No key provided",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err := command.Parse(tc.arguments)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go
--- a/cmd/gitlab-shell-authorized-keys-check/main.go
+++ b/cmd/gitlab-shell-authorized-keys-check/main.go
@@ -4,13 +4,13 @@
 	"fmt"
 	"os"
 
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
 func main() {
@@ -35,7 +35,7 @@
 	logCloser := logger.Configure(config)
 	defer logCloser.Close()
 
-	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
+	cmd, err := cmd.New(os.Args[1:], config, readWriter)
 	if err != nil {
 		// For now this could happen if `SSH_CONNECTION` is not set on
 		// the environment
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command.go b/cmd/gitlab-shell-authorized-principals-check/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command.go
@@ -0,0 +1,37 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+)
+
+func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(arguments)
+	if err != nil {
+		return nil, err
+	}
+
+	if cmd := build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func Parse(arguments []string) (*commandargs.AuthorizedPrincipals, error) {
+	args := &commandargs.AuthorizedPrincipals{Arguments: arguments}
+
+	if err := args.Parse(); err != nil {
+		return nil, err
+	}
+
+	return args, nil
+}
+
+func build(args *commandargs.AuthorizedPrincipals, config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	return &authorizedprincipals.Command{Config: config, Args: args, ReadWriter: readWriter}
+}
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command_test.go b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
@@ -0,0 +1,114 @@
+package command_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	authorizedPrincipalsExec = &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}
+	basicConfig              = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a AuthorizedPrincipals command",
+			executable:   authorizedPrincipalsExec,
+			arguments:    []string{"key", "principal"},
+			config:       basicConfig,
+			expectedType: &authorizedprincipals.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := cmd.New(tc.arguments, tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
+
+func TestParseSuccess(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		expectedArgs commandargs.CommandArgs
+		expectError  bool
+	}{
+		{
+			desc:         "It parses authorized-principals command",
+			executable:   &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:    []string{"key", "principal-1", "principal-2"},
+			expectedArgs: &commandargs.AuthorizedPrincipals{Arguments: []string{"key", "principal-1", "principal-2"}, KeyId: "key", Principals: []string{"principal-1", "principal-2"}},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := cmd.Parse(tc.arguments)
+
+			if !tc.expectError {
+				require.NoError(t, err)
+				require.Equal(t, tc.expectedArgs, result)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
+
+func TestParseFailure(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		arguments     []string
+		expectedError string
+	}{
+		{
+			desc:          "With not enough arguments for the AuthorizedPrincipalsCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:     []string{"key"},
+			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-principals-check <key-id> <principal1> [<principal2>...]",
+		},
+		{
+			desc:          "With missing key_id for the AuthorizedPrincipalsCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:     []string{"", "principal"},
+			expectedError: "# No key_id provided",
+		},
+		{
+			desc:          "With blank principal for the AuthorizedPrincipalsCheck",
+			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
+			arguments:     []string{"key", "principal", ""},
+			expectedError: "# An invalid principal was provided",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err := cmd.Parse(tc.arguments)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go
--- a/cmd/gitlab-shell-authorized-principals-check/main.go
+++ b/cmd/gitlab-shell-authorized-principals-check/main.go
@@ -4,13 +4,13 @@
 	"fmt"
 	"os"
 
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
 func main() {
@@ -35,7 +35,7 @@
 	logCloser := logger.Configure(config)
 	defer logCloser.Close()
 
-	cmd, err := command.New(executable, os.Args[1:], sshenv.Env{}, config, readWriter)
+	cmd, err := cmd.New(os.Args[1:], config, readWriter)
 	if err != nil {
 		// For now this could happen if `SSH_CONNECTION` is not set on
 		// the environment
diff --git a/cmd/gitlab-shell/command/command.go b/cmd/gitlab-shell/command/command.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell/command/command.go
@@ -0,0 +1,81 @@
+package command
+
+import (
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/hg"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+func New(arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(arguments, env)
+	if err != nil {
+		return nil, err
+	}
+
+	if cmd := Build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func NewWithKey(gitlabKeyId string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
+	args, err := Parse(nil, env)
+	if err != nil {
+		return nil, err
+	}
+
+	args.GitlabKeyId = gitlabKeyId
+	if cmd := Build(args, config, readWriter); cmd != nil {
+		return cmd, nil
+	}
+
+	return nil, disallowedcommand.Error
+}
+
+func Parse(arguments []string, env sshenv.Env) (*commandargs.Shell, error) {
+	args := &commandargs.Shell{Arguments: arguments, Env: env}
+
+	if err := args.Parse(); err != nil {
+		return nil, err
+	}
+
+	return args, nil
+}
+
+func Build(args *commandargs.Shell, config *config.Config, readWriter *readwriter.ReadWriter) command.Command {
+	switch args.CommandType {
+	case commandargs.Discover:
+		return &discover.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.TwoFactorRecover:
+		return &twofactorrecover.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.Hg:
+		return &hg.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.TwoFactorVerify:
+		return &twofactorverify.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.LfsAuthenticate:
+		return &lfsauthenticate.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.ReceivePack:
+		return &receivepack.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.UploadPack:
+		return &uploadpack.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.UploadArchive:
+		return &uploadarchive.Command{Config: config, Args: args, ReadWriter: readWriter}
+	case commandargs.PersonalAccessToken:
+		return &personalaccesstoken.Command{Config: config, Args: args, ReadWriter: readWriter}
+	}
+
+	return nil
+}
diff --git a/cmd/gitlab-shell/command/command_test.go b/cmd/gitlab-shell/command/command_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/gitlab-shell/command/command_test.go
@@ -0,0 +1,302 @@
+package command_test
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+)
+
+var (
+	gitlabShellExec = &executable.Executable{Name: executable.GitlabShell}
+	basicConfig     = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
+)
+
+func TestNew(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		config       *config.Config
+		expectedType interface{}
+	}{
+		{
+			desc:         "it returns a Discover command",
+			executable:   gitlabShellExec,
+			env:          buildEnv(""),
+			config:       basicConfig,
+			expectedType: &discover.Command{},
+		},
+		{
+			desc:         "it returns a TwoFactorRecover command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("2fa_recovery_codes"),
+			config:       basicConfig,
+			expectedType: &twofactorrecover.Command{},
+		},
+		{
+			desc:         "it returns a TwoFactorVerify command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("2fa_verify"),
+			config:       basicConfig,
+			expectedType: &twofactorverify.Command{},
+		},
+		{
+			desc:         "it returns an LfsAuthenticate command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-lfs-authenticate"),
+			config:       basicConfig,
+			expectedType: &lfsauthenticate.Command{},
+		},
+		{
+			desc:         "it returns a ReceivePack command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-receive-pack"),
+			config:       basicConfig,
+			expectedType: &receivepack.Command{},
+		},
+		{
+			desc:         "it returns an UploadPack command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-upload-pack"),
+			config:       basicConfig,
+			expectedType: &uploadpack.Command{},
+		},
+		{
+			desc:         "it returns an UploadArchive command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("git-upload-archive"),
+			config:       basicConfig,
+			expectedType: &uploadarchive.Command{},
+		},
+		{
+			desc:         "it returns a PersonalAccessToken command",
+			executable:   gitlabShellExec,
+			env:          buildEnv("personal_access_token"),
+			config:       basicConfig,
+			expectedType: &personalaccesstoken.Command{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := cmd.New(tc.arguments, tc.env, tc.config, nil)
+
+			require.NoError(t, err)
+			require.IsType(t, tc.expectedType, command)
+		})
+	}
+}
+
+func TestFailingNew(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		expectedError error
+	}{
+		{
+			desc:          "Parsing environment failed",
+			executable:    gitlabShellExec,
+			expectedError: errors.New("Only SSH allowed"),
+		},
+		{
+			desc:          "Unknown command given",
+			executable:    gitlabShellExec,
+			env:           buildEnv("unknown"),
+			expectedError: disallowedcommand.Error,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			command, err := cmd.New([]string{}, tc.env, basicConfig, nil)
+			require.Nil(t, command)
+			require.Equal(t, tc.expectedError, err)
+		})
+	}
+}
+
+func buildEnv(command string) sshenv.Env {
+	return sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: command,
+	}
+}
+
+func TestParseSuccess(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		executable   *executable.Executable
+		env          sshenv.Env
+		arguments    []string
+		expectedArgs commandargs.CommandArgs
+		expectError  bool
+	}{
+		{
+			desc:         "It sets discover as the command when the command string was empty",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{}, CommandType: commandargs.Discover, Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the key id in any passed arguments",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the key id only if the argument is of <key-id> format",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "username-key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "username-key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the key id if the key is listed as the last argument",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "gitlab-shell -c key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "gitlab-shell -c key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the username if the username is listed as the last argument",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "gitlab-shell -c username-jane-doe"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "gitlab-shell -c username-jane-doe"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "jane-doe", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the key id only if the last argument is of <key-id> format",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "gitlab-shell -c username-key-123"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "gitlab-shell -c username-key-123"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It finds the username in any passed arguments",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
+			arguments:    []string{"hello", "username-jane-doe"},
+			expectedArgs: &commandargs.Shell{Arguments: []string{"hello", "username-jane-doe"}, SshArgs: []string{}, CommandType: commandargs.Discover, GitlabUsername: "jane-doe", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
+		},
+		{
+			desc:         "It parses 2fa_recovery_codes command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"2fa_recovery_codes"}, CommandType: commandargs.TwoFactorRecover, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"}},
+		},
+		{
+			desc:         "It parses git-receive-pack command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"}},
+		},
+		{
+			desc:         "It parses git-receive-pack command and a project with single quotes",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"}},
+		},
+		{
+			desc:         `It parses "git receive-pack" command`,
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`}},
+		},
+		{
+			desc:         `It parses a command followed by control characters`,
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: commandargs.ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`}},
+		},
+		{
+			desc:         "It parses git-upload-pack command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-upload-pack", "group/repo"}, CommandType: commandargs.UploadPack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`}},
+		},
+		{
+			desc:         "It parses git-upload-archive command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-upload-archive", "group/repo"}, CommandType: commandargs.UploadArchive, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"}},
+		},
+		{
+			desc:         "It parses git-lfs-authenticate command",
+			executable:   &executable.Executable{Name: executable.GitlabShell},
+			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"},
+			arguments:    []string{},
+			expectedArgs: &commandargs.Shell{Arguments: []string{}, SshArgs: []string{"git-lfs-authenticate", "group/repo", "download"}, CommandType: commandargs.LfsAuthenticate, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"}},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := cmd.Parse(tc.arguments, tc.env)
+
+			if !tc.expectError {
+				require.NoError(t, err)
+				require.Equal(t, tc.expectedArgs, result)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
+
+func TestParseFailure(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		executable    *executable.Executable
+		env           sshenv.Env
+		arguments     []string
+		expectedError string
+	}{
+		{
+			desc:          "It fails if SSH connection is not set",
+			executable:    &executable.Executable{Name: executable.GitlabShell},
+			arguments:     []string{},
+			expectedError: "Only SSH allowed",
+		},
+		{
+			desc:          "It fails if SSH command is invalid",
+			executable:    &executable.Executable{Name: executable.GitlabShell},
+			env:           sshenv.Env{IsSSHConnection: true, OriginalCommand: `git receive-pack "`},
+			arguments:     []string{},
+			expectedError: "Invalid SSH command: invalid command line string",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err := cmd.Parse(tc.arguments, tc.env)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -3,7 +3,11 @@
 import (
 	"fmt"
 	"os"
+	"reflect"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -50,7 +54,7 @@
 	defer logCloser.Close()
 
 	env := sshenv.NewFromEnv()
-	cmd, err := command.New(executable, os.Args[1:], env, config, readWriter)
+	cmd, err := shellCmd.New(os.Args[1:], env, config, readWriter)
 	if err != nil {
 		// For now this could happen if `SSH_CONNECTION` is not set on
 		// the environment
@@ -61,8 +65,15 @@
 	ctx, finished := command.Setup(executable.Name, config)
 	defer finished()
 
-	if err = cmd.Execute(ctx); err != nil {
+	cmdName := reflect.TypeOf(cmd).String()
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("gitlab-shell: main: executing command")
+
+	if err := cmd.Execute(ctx); err != nil {
+		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
 		console.DisplayWarningMessage(err.Error(), readWriter.ErrOut)
 		os.Exit(1)
 	}
+
+	ctxlog.Info("gitlab-shell: main: command executed successfully")
 }
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -8,7 +8,6 @@
 	"encoding/pem"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"net/http/httptest"
@@ -112,7 +111,7 @@
 		case "/api/v4/internal/two_factor_otp_check":
 			fmt.Fprint(w, `{"success": true}`)
 		case "/api/v4/internal/allowed":
-			body, err := ioutil.ReadFile(filepath.Join(testhelper.TestRoot, "responses/allowed_without_console_messages.json"))
+			body, err := os.ReadFile(filepath.Join(testhelper.TestRoot, "responses/allowed_without_console_messages.json"))
 			require.NoError(t, err)
 
 			response := strings.Replace(string(body), "GITALY_REPOSITORY", testRepo, 1)
@@ -198,7 +197,7 @@
 func configureSSHD(t *testing.T, apiServer string) (string, ed25519.PublicKey) {
 	t.Helper()
 
-	dir, err := ioutil.TempDir("", "gitlab-sshd-acceptance-test-")
+	dir, err := os.MkdirTemp("", "gitlab-sshd-acceptance-test-")
 	require.NoError(t, err)
 	t.Cleanup(func() { os.RemoveAll(dir) })
 
@@ -209,11 +208,11 @@
 	require.NoError(t, err)
 
 	configFileData := genServerConfig(apiServer, hostKeyFile)
-	require.NoError(t, ioutil.WriteFile(configFile, configFileData, 0644))
+	require.NoError(t, os.WriteFile(configFile, configFileData, 0644))
 
 	block := &pem.Block{Type: "OPENSSH PRIVATE KEY", Bytes: edkey.MarshalED25519PrivateKey(priv)}
 	hostKeyData := pem.EncodeToMemory(block)
-	require.NoError(t, ioutil.WriteFile(hostKeyFile, hostKeyData, 0400))
+	require.NoError(t, os.WriteFile(hostKeyFile, hostKeyData, 0400))
 
 	return dir, pub
 }
@@ -326,7 +325,7 @@
 	_, err = fmt.Fprintln(stdin, "yes")
 	require.NoError(t, err)
 
-	output, err := ioutil.ReadAll(stdout)
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 	require.Equal(t, `
 Your two-factor authentication recovery codes are:
@@ -365,7 +364,7 @@
 	_, err = fmt.Fprintln(stdin, "otp123")
 	require.NoError(t, err)
 
-	output, err := ioutil.ReadAll(stdout)
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 	require.Equal(t, "OTP validation successful. Git operations are now allowed.\n", string(output))
 }
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -97,7 +97,7 @@
 		sig := <-done
 		signal.Reset(syscall.SIGINT, syscall.SIGTERM)
 
-		log.WithFields(log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
+		log.WithContextFields(ctx, log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
 
 		server.Shutdown()
 
diff --git a/docker-compose.yml b/docker-compose.yml
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -3,6 +3,6 @@
   gitaly:
     environment:
       - GITALY_TESTING_NO_GIT_HOOKS=1
-    image: registry.gitlab.com/gitlab-org/build/cng/gitaly:latest
+    image: registry.gitlab.com/gitlab-org/build/cng/gitaly:master
     ports:
       - '8075:8075'
diff --git a/heptapod-ci.yml b/heptapod-ci.yml
--- a/heptapod-ci.yml
+++ b/heptapod-ci.yml
@@ -31,7 +31,7 @@
     - go version
     - which go
   services:
-    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:latest
+    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:v14.5.2
       # Disable the hooks so we don't have to stub the GitLab API
       command: ["bash", "-c", "mkdir -p /home/git/repositories && rm -rf /srv/gitlab-shell/hooks/* && exec /usr/bin/env GITALY_TESTING_NO_GIT_HOOKS=1 /scripts/process-wrapper"]
       alias: gitaly
diff --git a/internal/command/command.go b/internal/command/command.go
--- a/internal/command/command.go
+++ b/internal/command/command.go
@@ -3,24 +3,7 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/hg"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/tracing"
 )
@@ -29,19 +12,6 @@
 	Execute(ctx context.Context) error
 }
 
-func New(e *executable.Executable, arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (Command, error) {
-	args, err := commandargs.Parse(e, arguments, env)
-	if err != nil {
-		return nil, err
-	}
-
-	if cmd := buildCommand(e, args, config, readWriter); cmd != nil {
-		return cmd, nil
-	}
-
-	return nil, disallowedcommand.Error
-}
-
 // Setup() initializes tracing from the configuration file and generates a
 // background context from which all other contexts in the process should derive
 // from, as it has a service name and initial correlation ID set.
@@ -77,55 +47,3 @@
 		closer.Close()
 	}
 }
-
-func buildCommand(e *executable.Executable, args commandargs.CommandArgs, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	switch e.Name {
-	case executable.GitlabShell:
-		return BuildShellCommand(args.(*commandargs.Shell), config, readWriter)
-	case executable.AuthorizedKeysCheck:
-		return buildAuthorizedKeysCommand(args.(*commandargs.AuthorizedKeys), config, readWriter)
-	case executable.AuthorizedPrincipalsCheck:
-		return buildAuthorizedPrincipalsCommand(args.(*commandargs.AuthorizedPrincipals), config, readWriter)
-	case executable.Healthcheck:
-		return buildHealthcheckCommand(config, readWriter)
-	}
-
-	return nil
-}
-
-func BuildShellCommand(args *commandargs.Shell, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	switch args.CommandType {
-	case commandargs.Discover:
-		return &discover.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.TwoFactorRecover:
-		return &twofactorrecover.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.TwoFactorVerify:
-		return &twofactorverify.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.Hg:
-		return &hg.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.LfsAuthenticate:
-		return &lfsauthenticate.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.ReceivePack:
-		return &receivepack.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.UploadPack:
-		return &uploadpack.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.UploadArchive:
-		return &uploadarchive.Command{Config: config, Args: args, ReadWriter: readWriter}
-	case commandargs.PersonalAccessToken:
-		return &personalaccesstoken.Command{Config: config, Args: args, ReadWriter: readWriter}
-	}
-
-	return nil
-}
-
-func buildAuthorizedKeysCommand(args *commandargs.AuthorizedKeys, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	return &authorizedkeys.Command{Config: config, Args: args, ReadWriter: readWriter}
-}
-
-func buildAuthorizedPrincipalsCommand(args *commandargs.AuthorizedPrincipals, config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	return &authorizedprincipals.Command{Config: config, Args: args, ReadWriter: readWriter}
-}
-
-func buildHealthcheckCommand(config *config.Config, readWriter *readwriter.ReadWriter) Command {
-	return &healthcheck.Command{Config: config, ReadWriter: readWriter}
-}
diff --git a/internal/command/command_test.go b/internal/command/command_test.go
--- a/internal/command/command_test.go
+++ b/internal/command/command_test.go
@@ -1,173 +1,15 @@
 package command
 
 import (
-	"errors"
 	"os"
 	"testing"
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
-var (
-	authorizedKeysExec       = &executable.Executable{Name: executable.AuthorizedKeysCheck}
-	authorizedPrincipalsExec = &executable.Executable{Name: executable.AuthorizedPrincipalsCheck}
-	checkExec                = &executable.Executable{Name: executable.Healthcheck}
-	gitlabShellExec          = &executable.Executable{Name: executable.GitlabShell}
-
-	basicConfig    = &config.Config{GitlabUrl: "http+unix://gitlab.socket"}
-	advancedConfig = &config.Config{GitlabUrl: "http+unix://gitlab.socket", SslCertDir: "/tmp/certs"}
-)
-
-func buildEnv(command string) sshenv.Env {
-	return sshenv.Env{
-		IsSSHConnection: true,
-		OriginalCommand: command,
-	}
-}
-
-func TestNew(t *testing.T) {
-	testCases := []struct {
-		desc         string
-		executable   *executable.Executable
-		env          sshenv.Env
-		arguments    []string
-		config       *config.Config
-		expectedType interface{}
-	}{
-		{
-			desc:         "it returns a Discover command",
-			executable:   gitlabShellExec,
-			env:          buildEnv(""),
-			config:       basicConfig,
-			expectedType: &discover.Command{},
-		},
-		{
-			desc:         "it returns a TwoFactorRecover command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("2fa_recovery_codes"),
-			config:       basicConfig,
-			expectedType: &twofactorrecover.Command{},
-		},
-		{
-			desc:         "it returns a TwoFactorVerify command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("2fa_verify"),
-			config:       basicConfig,
-			expectedType: &twofactorverify.Command{},
-		},
-		{
-			desc:         "it returns an LfsAuthenticate command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-lfs-authenticate"),
-			config:       basicConfig,
-			expectedType: &lfsauthenticate.Command{},
-		},
-		{
-			desc:         "it returns a ReceivePack command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-receive-pack"),
-			config:       basicConfig,
-			expectedType: &receivepack.Command{},
-		},
-		{
-			desc:         "it returns an UploadPack command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-upload-pack"),
-			config:       basicConfig,
-			expectedType: &uploadpack.Command{},
-		},
-		{
-			desc:         "it returns an UploadArchive command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("git-upload-archive"),
-			config:       basicConfig,
-			expectedType: &uploadarchive.Command{},
-		},
-		{
-			desc:         "it returns a Healthcheck command",
-			executable:   checkExec,
-			config:       basicConfig,
-			expectedType: &healthcheck.Command{},
-		},
-		{
-			desc:         "it returns a AuthorizedKeys command",
-			executable:   authorizedKeysExec,
-			arguments:    []string{"git", "git", "key"},
-			config:       basicConfig,
-			expectedType: &authorizedkeys.Command{},
-		},
-		{
-			desc:         "it returns a AuthorizedPrincipals command",
-			executable:   authorizedPrincipalsExec,
-			arguments:    []string{"key", "principal"},
-			config:       basicConfig,
-			expectedType: &authorizedprincipals.Command{},
-		},
-		{
-			desc:         "it returns a PersonalAccessToken command",
-			executable:   gitlabShellExec,
-			env:          buildEnv("personal_access_token"),
-			config:       basicConfig,
-			expectedType: &personalaccesstoken.Command{},
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			command, err := New(tc.executable, tc.arguments, tc.env, tc.config, nil)
-
-			require.NoError(t, err)
-			require.IsType(t, tc.expectedType, command)
-		})
-	}
-}
-
-func TestFailingNew(t *testing.T) {
-	testCases := []struct {
-		desc          string
-		executable    *executable.Executable
-		env           sshenv.Env
-		expectedError error
-	}{
-		{
-			desc:          "Parsing environment failed",
-			executable:    gitlabShellExec,
-			expectedError: errors.New("Only SSH allowed"),
-		},
-		{
-			desc:          "Unknown command given",
-			executable:    gitlabShellExec,
-			env:           buildEnv("unknown"),
-			expectedError: disallowedcommand.Error,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			command, err := New(tc.executable, []string{}, tc.env, basicConfig, nil)
-			require.Nil(t, command)
-			require.Equal(t, tc.expectedError, err)
-		})
-	}
-}
-
 func TestSetup(t *testing.T) {
 	testCases := []struct {
 		name                  string
diff --git a/internal/command/commandargs/command_args.go b/internal/command/commandargs/command_args.go
--- a/internal/command/commandargs/command_args.go
+++ b/internal/command/commandargs/command_args.go
@@ -1,32 +1,8 @@
 package commandargs
 
-import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-)
-
 type CommandType string
 
 type CommandArgs interface {
 	Parse() error
 	GetArguments() []string
 }
-
-func Parse(e *executable.Executable, arguments []string, env sshenv.Env) (CommandArgs, error) {
-	var args CommandArgs = &GenericArgs{Arguments: arguments}
-
-	switch e.Name {
-	case executable.GitlabShell:
-		args = &Shell{Arguments: arguments, Env: env}
-	case executable.AuthorizedKeysCheck:
-		args = &AuthorizedKeys{Arguments: arguments}
-	case executable.AuthorizedPrincipalsCheck:
-		args = &AuthorizedPrincipals{Arguments: arguments}
-	}
-
-	if err := args.Parse(); err != nil {
-		return nil, err
-	}
-
-	return args, nil
-}
diff --git a/internal/command/commandargs/command_args_test.go b/internal/command/commandargs/command_args_test.go
deleted file mode 100644
--- a/internal/command/commandargs/command_args_test.go
+++ /dev/null
@@ -1,192 +0,0 @@
-package commandargs
-
-import (
-	"testing"
-
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestParseSuccess(t *testing.T) {
-	testCases := []struct {
-		desc         string
-		executable   *executable.Executable
-		env          sshenv.Env
-		arguments    []string
-		expectedArgs CommandArgs
-	}{
-		{
-			desc:         "It sets discover as the command when the command string was empty",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{}, CommandType: Discover, Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It finds the key id in any passed arguments",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{"hello", "key-123"},
-			expectedArgs: &Shell{Arguments: []string{"hello", "key-123"}, SshArgs: []string{}, CommandType: Discover, GitlabKeyId: "123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It finds the key id only if the argument is of <key-id> format",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{"hello", "username-key-123"},
-			expectedArgs: &Shell{Arguments: []string{"hello", "username-key-123"}, SshArgs: []string{}, CommandType: Discover, GitlabUsername: "key-123", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It finds the username in any passed arguments",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"},
-			arguments:    []string{"hello", "username-jane-doe"},
-			expectedArgs: &Shell{Arguments: []string{"hello", "username-jane-doe"}, SshArgs: []string{}, CommandType: Discover, GitlabUsername: "jane-doe", Env: sshenv.Env{IsSSHConnection: true, RemoteAddr: "1"}},
-		}, {
-			desc:         "It parses 2fa_recovery_codes command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"2fa_recovery_codes"}, CommandType: TwoFactorRecover, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "2fa_recovery_codes"}},
-		}, {
-			desc:         "It parses git-receive-pack command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack group/repo"}},
-		}, {
-			desc:         "It parses git-receive-pack command and a project with single quotes",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-receive-pack 'group/repo'"}},
-		}, {
-			desc:         `It parses "git receive-pack" command`,
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack "group/repo"`}},
-		}, {
-			desc:         `It parses a command followed by control characters`,
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git-receive-pack group/repo; any command`}},
-		}, {
-			desc:         "It parses git-upload-pack command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-upload-pack", "group/repo"}, CommandType: UploadPack, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: `git upload-pack "group/repo"`}},
-		}, {
-			desc:         "It parses git-upload-archive command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-upload-archive", "group/repo"}, CommandType: UploadArchive, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-upload-archive 'group/repo'"}},
-		}, {
-			desc:         "It parses git-lfs-authenticate command",
-			executable:   &executable.Executable{Name: executable.GitlabShell},
-			env:          sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"},
-			arguments:    []string{},
-			expectedArgs: &Shell{Arguments: []string{}, SshArgs: []string{"git-lfs-authenticate", "group/repo", "download"}, CommandType: LfsAuthenticate, Env: sshenv.Env{IsSSHConnection: true, OriginalCommand: "git-lfs-authenticate 'group/repo' download"}},
-		}, {
-			desc:         "It parses authorized-keys command",
-			executable:   &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:    []string{"git", "git", "key"},
-			expectedArgs: &AuthorizedKeys{Arguments: []string{"git", "git", "key"}, ExpectedUser: "git", ActualUser: "git", Key: "key"},
-		}, {
-			desc:         "It parses authorized-principals command",
-			executable:   &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:    []string{"key", "principal-1", "principal-2"},
-			expectedArgs: &AuthorizedPrincipals{Arguments: []string{"key", "principal-1", "principal-2"}, KeyId: "key", Principals: []string{"principal-1", "principal-2"}},
-		}, {
-			desc:         "Unknown executable",
-			executable:   &executable.Executable{Name: "unknown"},
-			arguments:    []string{},
-			expectedArgs: &GenericArgs{Arguments: []string{}},
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			result, err := Parse(tc.executable, tc.arguments, tc.env)
-
-			require.NoError(t, err)
-			require.Equal(t, tc.expectedArgs, result)
-		})
-	}
-}
-
-func TestParseFailure(t *testing.T) {
-	testCases := []struct {
-		desc          string
-		executable    *executable.Executable
-		env           sshenv.Env
-		arguments     []string
-		expectedError string
-	}{
-		{
-			desc:          "It fails if SSH connection is not set",
-			executable:    &executable.Executable{Name: executable.GitlabShell},
-			arguments:     []string{},
-			expectedError: "Only SSH allowed",
-		},
-		{
-			desc:          "It fails if SSH command is invalid",
-			executable:    &executable.Executable{Name: executable.GitlabShell},
-			env:           sshenv.Env{IsSSHConnection: true, OriginalCommand: `git receive-pack "`},
-			arguments:     []string{},
-			expectedError: "Invalid SSH command",
-		},
-		{
-			desc:          "With not enough arguments for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user"},
-			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
-		},
-		{
-			desc:          "With too many arguments for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user", "user", "key", "something-else"},
-			expectedError: "# Insufficient arguments. 4. Usage\n#\tgitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>",
-		},
-		{
-			desc:          "With missing username for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user", "", "key"},
-			expectedError: "# No username provided",
-		},
-		{
-			desc:          "With missing key for the AuthorizedKeysCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedKeysCheck},
-			arguments:     []string{"user", "user", ""},
-			expectedError: "# No key provided",
-		},
-		{
-			desc:          "With not enough arguments for the AuthorizedPrincipalsCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:     []string{"key"},
-			expectedError: "# Insufficient arguments. 1. Usage\n#\tgitlab-shell-authorized-principals-check <key-id> <principal1> [<principal2>...]",
-		},
-		{
-			desc:          "With missing key_id for the AuthorizedPrincipalsCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:     []string{"", "principal"},
-			expectedError: "# No key_id provided",
-		},
-		{
-			desc:          "With blank principal for the AuthorizedPrincipalsCheck",
-			executable:    &executable.Executable{Name: executable.AuthorizedPrincipalsCheck},
-			arguments:     []string{"key", "principal", ""},
-			expectedError: "# An invalid principal was provided",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			_, err := Parse(tc.executable, tc.arguments, tc.env)
-
-			require.EqualError(t, err, tc.expectedError)
-		})
-	}
-}
diff --git a/internal/command/commandargs/generic_args.go b/internal/command/commandargs/generic_args.go
deleted file mode 100644
--- a/internal/command/commandargs/generic_args.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package commandargs
-
-type GenericArgs struct {
-	Arguments []string
-}
-
-func (b *GenericArgs) Parse() error {
-	// Do nothing
-	return nil
-}
-
-func (b *GenericArgs) GetArguments() []string {
-	return b.Arguments
-}
diff --git a/internal/command/commandargs/shell.go b/internal/command/commandargs/shell.go
--- a/internal/command/commandargs/shell.go
+++ b/internal/command/commandargs/shell.go
@@ -1,8 +1,9 @@
 package commandargs
 
 import (
-	"errors"
+	"fmt"
 	"regexp"
+	"strings"
 
 	"github.com/mattn/go-shellwords"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
@@ -50,21 +51,16 @@
 
 func (s *Shell) validate() error {
 	if !s.Env.IsSSHConnection {
-		return errors.New("Only SSH allowed")
+		return fmt.Errorf("Only SSH allowed")
 	}
 
-	if !s.isValidSSHCommand() {
-		return errors.New("Invalid SSH command")
+	if err := s.ParseCommand(s.Env.OriginalCommand); err != nil {
+		return fmt.Errorf("Invalid SSH command: %w", err)
 	}
 
 	return nil
 }
 
-func (s *Shell) isValidSSHCommand() bool {
-	err := s.ParseCommand(s.Env.OriginalCommand)
-	return err == nil
-}
-
 func (s *Shell) parseWho() {
 	for _, argument := range s.Arguments {
 		if keyId := tryParseKeyId(argument); keyId != "" {
@@ -79,26 +75,29 @@
 	}
 }
 
-func tryParseKeyId(argument string) string {
-	matchInfo := whoKeyRegex.FindStringSubmatch(argument)
+func tryParse(r *regexp.Regexp, argument string) string {
+	// sshd may execute the session for AuthorizedKeysCommand in multiple ways:
+	// 1. key-id
+	// 2. /path/to/shell -c key-id
+	args := strings.Split(argument, " ")
+	lastArg := args[len(args)-1]
+
+	matchInfo := r.FindStringSubmatch(lastArg)
 	if len(matchInfo) == 2 {
 		// The first element is the full matched string
-		// The second element is the named `keyid`
+		// The second element is the named `keyid` or `username`
 		return matchInfo[1]
 	}
 
 	return ""
 }
 
+func tryParseKeyId(argument string) string {
+	return tryParse(whoKeyRegex, argument)
+}
+
 func tryParseUsername(argument string) string {
-	matchInfo := whoUsernameRegex.FindStringSubmatch(argument)
-	if len(matchInfo) == 2 {
-		// The first element is the full matched string
-		// The second element is the named `username`
-		return matchInfo[1]
-	}
-
-	return ""
+	return tryParse(whoUsernameRegex, argument)
 }
 
 func (s *Shell) ParseCommand(commandString string) error {
diff --git a/internal/command/lfsauthenticate/lfsauthenticate.go b/internal/command/lfsauthenticate/lfsauthenticate.go
--- a/internal/command/lfsauthenticate/lfsauthenticate.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate.go
@@ -6,6 +6,8 @@
 	"encoding/json"
 	"fmt"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
@@ -58,6 +60,11 @@
 	payload, err := c.authenticate(ctx, operation, repo, accessResponse.UserId)
 	if err != nil {
 		// return nothing just like Ruby's GitlabShell#lfs_authenticate does
+		log.WithContextFields(
+			ctx,
+			log.Fields{"operation": operation, "repo": repo, "user_id": accessResponse.UserId},
+		).WithError(err).Debug("lfsauthenticate: execute: LFS authentication failed")
+
 		return nil
 	}
 
diff --git a/internal/command/lfsauthenticate/lfsauthenticate_test.go b/internal/command/lfsauthenticate/lfsauthenticate_test.go
--- a/internal/command/lfsauthenticate/lfsauthenticate_test.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -70,7 +70,7 @@
 		{
 			Path: "/api/v4/internal/lfs_authenticate",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 				require.NoError(t, err)
 
@@ -94,7 +94,7 @@
 		{
 			Path: "/api/v4/internal/allowed",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 				require.NoError(t, err)
 
diff --git a/internal/command/personalaccesstoken/personalaccesstoken.go b/internal/command/personalaccesstoken/personalaccesstoken.go
--- a/internal/command/personalaccesstoken/personalaccesstoken.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken.go
@@ -8,6 +8,8 @@
 	"strings"
 	"time"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -38,6 +40,10 @@
 		return err
 	}
 
+	log.WithContextFields(ctx, log.Fields{
+		"token_args": c.TokenArgs,
+	}).Info("personalaccesstoken: execute: requesting token")
+
 	response, err := c.getPersonalAccessToken(ctx)
 	if err != nil {
 		return err
diff --git a/internal/command/personalaccesstoken/personalaccesstoken_test.go b/internal/command/personalaccesstoken/personalaccesstoken_test.go
--- a/internal/command/personalaccesstoken/personalaccesstoken_test.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -26,7 +26,7 @@
 		{
 			Path: "/api/v4/internal/personal_access_token",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/command/shared/accessverifier/accessverifier_test.go b/internal/command/shared/accessverifier/accessverifier_test.go
--- a/internal/command/shared/accessverifier/accessverifier_test.go
+++ b/internal/command/shared/accessverifier/accessverifier_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -27,7 +27,7 @@
 		{
 			Path: "/api/v4/internal/allowed",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var requestBody *accessverifier.Request
diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -4,19 +4,17 @@
 	"bytes"
 	"context"
 	"errors"
-
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-
 	"io"
 	"net/http"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
+	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/pktline"
-
-	"gitlab.com/gitlab-org/labkit/log"
 )
 
 type Request struct {
@@ -59,12 +57,12 @@
 	request.Data.UserId = response.Who
 
 	for _, endpoint := range data.ApiEndpoints {
-		fields := log.Fields{
+		ctxlog := log.WithContextFields(ctx, log.Fields{
 			"primary_repo": data.PrimaryRepo,
 			"endpoint":     endpoint,
-		}
+		})
 
-		log.WithFields(fields).Info("Performing custom action")
+		ctxlog.Info("customaction: processApiEndpoints: Performing custom action")
 
 		response, err := c.performRequest(ctx, client, endpoint, request)
 		if err != nil {
@@ -90,6 +88,10 @@
 		} else {
 			output = c.readFromStdinNoEOF()
 		}
+		ctxlog.WithFields(log.Fields{
+			"eof_sent":    c.EOFSent,
+			"stdin_bytes": len(output),
+		}).Debug("customaction: processApiEndpoints: stdin buffered")
 
 		request.Output = output
 	}
diff --git a/internal/command/shared/customaction/customaction_test.go b/internal/command/shared/customaction/customaction_test.go
--- a/internal/command/shared/customaction/customaction_test.go
+++ b/internal/command/shared/customaction/customaction_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -23,7 +23,7 @@
 		{
 			Path: "/geo/proxy/info_refs_receive_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
@@ -39,7 +39,7 @@
 		{
 			Path: "/geo/proxy/receive_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
@@ -92,7 +92,7 @@
 		{
 			Path: "/geo/proxy/info_refs_upload_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
@@ -108,7 +108,7 @@
 		{
 			Path: "/geo/proxy/upload_pack",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var request *Request
diff --git a/internal/command/twofactorrecover/twofactorrecover.go b/internal/command/twofactorrecover/twofactorrecover.go
--- a/internal/command/twofactorrecover/twofactorrecover.go
+++ b/internal/command/twofactorrecover/twofactorrecover.go
@@ -6,6 +6,8 @@
 	"io"
 	"strings"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -21,9 +23,14 @@
 }
 
 func (c *Command) Execute(ctx context.Context) error {
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.Debug("twofactorrecover: execute: Waiting for user input")
+
 	if c.canContinue() {
+		ctxlog.Debug("twofactorrecover: execute: User chose to continue")
 		c.displayRecoveryCodes(ctx)
 	} else {
+		ctxlog.Debug("twofactorrecover: execute: User chose not to continue")
 		fmt.Fprintln(c.ReadWriter.Out, "\nNew recovery codes have *not* been generated. Existing codes will remain valid.")
 	}
 
@@ -43,9 +50,12 @@
 }
 
 func (c *Command) displayRecoveryCodes(ctx context.Context) {
+	ctxlog := log.ContextLogger(ctx)
+
 	codes, err := c.getRecoveryCodes(ctx)
 
 	if err == nil {
+		ctxlog.Debug("twofactorrecover: displayRecoveryCodes: recovery codes successfully generated")
 		messageWithCodes :=
 			"\nYour two-factor authentication recovery codes are:\n\n" +
 				strings.Join(codes, "\n") +
@@ -54,6 +64,7 @@
 				"a new device so you do not lose access to your account again.\n"
 		fmt.Fprint(c.ReadWriter.Out, messageWithCodes)
 	} else {
+		ctxlog.WithError(err).Error("twofactorrecover: displayRecoveryCodes: failed to generate recovery codes")
 		fmt.Fprintf(c.ReadWriter.Out, "\nAn error occurred while trying to generate new recovery codes.\n%v\n", err)
 	}
 }
diff --git a/internal/command/twofactorrecover/twofactorrecover_test.go b/internal/command/twofactorrecover/twofactorrecover_test.go
--- a/internal/command/twofactorrecover/twofactorrecover_test.go
+++ b/internal/command/twofactorrecover/twofactorrecover_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strings"
 	"testing"
@@ -27,7 +27,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_recovery_codes",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -5,6 +5,8 @@
 	"fmt"
 	"io"
 
+	"gitlab.com/gitlab-org/labkit/log"
+
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -18,11 +20,18 @@
 }
 
 func (c *Command) Execute(ctx context.Context) error {
-	err := c.verifyOTP(ctx, c.getOTP())
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.Info("twofactorverify: execute: waiting for user input")
+	otp := c.getOTP()
+
+	ctxlog.Info("twofactorverify: execute: verifying entered OTP")
+	err := c.verifyOTP(ctx, otp)
 	if err != nil {
+		ctxlog.WithError(err).Error("twofactorverify: execute: OTP verification failed")
 		return err
 	}
 
+	ctxlog.WithError(err).Info("twofactorverify: execute: OTP verified")
 	return nil
 }
 
diff --git a/internal/command/twofactorverify/twofactorverify_test.go b/internal/command/twofactorverify/twofactorverify_test.go
--- a/internal/command/twofactorverify/twofactorverify_test.go
+++ b/internal/command/twofactorverify/twofactorverify_test.go
@@ -4,7 +4,7 @@
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -22,7 +22,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_otp_check",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -2,7 +2,6 @@
 
 import (
 	"errors"
-	"io/ioutil"
 	"net/url"
 	"os"
 	"path"
@@ -73,6 +72,7 @@
 	RootDir               string
 	LogFile               string `yaml:"log_file,omitempty"`
 	LogFormat             string `yaml:"log_format,omitempty"`
+	LogLevel              string `yaml:"log_level,omitempty"`
 	GitlabUrl             string `yaml:"gitlab_url"`
 	GitlabRelativeURLRoot string `yaml:"gitlab_relative_url_root"`
 	GitlabTracing         string `yaml:"gitlab_tracing"`
@@ -94,6 +94,7 @@
 	DefaultConfig = Config{
 		LogFile:   "gitlab-shell.log",
 		LogFormat: "json",
+		LogLevel:  "info",
 		Server:    DefaultServerConfig,
 		User:      "git",
 	}
@@ -174,7 +175,7 @@
 	*cfg = DefaultConfig
 	cfg.RootDir = filepath.Dir(path)
 
-	configBytes, err := ioutil.ReadFile(path)
+	configBytes, err := os.ReadFile(path)
 	if err != nil {
 		return nil, err
 	}
@@ -218,7 +219,7 @@
 		cfg.SecretFilePath = path.Join(cfg.RootDir, cfg.SecretFilePath)
 	}
 
-	secretFileContent, err := ioutil.ReadFile(cfg.SecretFilePath)
+	secretFileContent, err := os.ReadFile(cfg.SecretFilePath)
 	if err != nil {
 		return err
 	}
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -32,7 +32,7 @@
 	client, err := config.HttpClient()
 	require.NoError(t, err)
 
-	_, err = client.Get("http://host.com/path")
+	_, err = client.Get(url)
 	require.NoError(t, err)
 
 	ms, err := prometheus.DefaultGatherer.Gather()
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -3,8 +3,9 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
+	"os"
 	"path"
 	"testing"
 
@@ -165,14 +166,14 @@
 func setup(t *testing.T, allowedPayload string) *Client {
 	testhelper.PrepareTestRootDir(t)
 
-	body, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
+	body, err := os.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
 	require.NoError(t, err)
 
 	var bodyWithPayload []byte
 
 	if allowedPayload != "" {
 		allowedWithPayloadPath := path.Join(testhelper.TestRoot, allowedPayload)
-		bodyWithPayload, err = ioutil.ReadFile(allowedWithPayloadPath)
+		bodyWithPayload, err = os.ReadFile(allowedWithPayloadPath)
 		require.NoError(t, err)
 	}
 
@@ -180,7 +181,7 @@
 		{
 			Path: "/api/v4/internal/allowed",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				require.NoError(t, err)
 
 				var requestBody *Request
diff --git a/internal/gitlabnet/lfsauthenticate/client_test.go b/internal/gitlabnet/lfsauthenticate/client_test.go
--- a/internal/gitlabnet/lfsauthenticate/client_test.go
+++ b/internal/gitlabnet/lfsauthenticate/client_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -24,7 +24,7 @@
 		{
 			Path: "/api/v4/internal/lfs_authenticate",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 				require.NoError(t, err)
 
diff --git a/internal/gitlabnet/personalaccesstoken/client_test.go b/internal/gitlabnet/personalaccesstoken/client_test.go
--- a/internal/gitlabnet/personalaccesstoken/client_test.go
+++ b/internal/gitlabnet/personalaccesstoken/client_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -24,7 +24,7 @@
 		{
 			Path: "/api/v4/internal/personal_access_token",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/gitlabnet/twofactorrecover/client_test.go b/internal/gitlabnet/twofactorrecover/client_test.go
--- a/internal/gitlabnet/twofactorrecover/client_test.go
+++ b/internal/gitlabnet/twofactorrecover/client_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -24,7 +24,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_recovery_codes",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/gitlabnet/twofactorverify/client_test.go b/internal/gitlabnet/twofactorverify/client_test.go
--- a/internal/gitlabnet/twofactorverify/client_test.go
+++ b/internal/gitlabnet/twofactorverify/client_test.go
@@ -3,11 +3,12 @@
 import (
 	"context"
 	"encoding/json"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
@@ -20,7 +21,7 @@
 		{
 			Path: "/api/v4/internal/two_factor_otp_check",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := ioutil.ReadAll(r.Body)
+				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
 
 				require.NoError(t, err)
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -9,7 +9,9 @@
 	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
 	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
 	"google.golang.org/grpc"
+	grpccodes "google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
+	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
@@ -43,12 +45,25 @@
 func (gc *GitalyCommand) RunGitalyCommand(ctx context.Context, handler GitalyHandlerFunc) error {
 	conn, err := getConn(ctx, gc)
 	if err != nil {
+		log.ContextLogger(ctx).WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Failed to get connection to execute Git command")
+
 		return err
 	}
 	defer conn.Close()
 
 	childCtx := withOutgoingMetadata(ctx, gc.Features)
-	_, err = handler(childCtx, conn)
+	ctxlog := log.ContextLogger(childCtx)
+	exitStatus, err := handler(childCtx, conn)
+
+	if err != nil {
+		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
+			ctxlog.WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Gitaly is unavailable")
+
+			return fmt.Errorf("The git server, Gitaly, is not available at this time. Please contact your administrator.")
+		}
+
+		ctxlog.WithError(err).WithFields(log.Fields{"exit_status": exitStatus}).Error("Failed to execute Git command")
+	}
 
 	return err
 }
@@ -110,7 +125,7 @@
 	if serviceName == "" {
 		serviceName = "gitlab-shell-unknown"
 
-		log.WithFields(log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
+		log.WithContextFields(ctx, log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
 	}
 
 	serviceName = fmt.Sprintf("%s-%s", serviceName, gc.ServiceName)
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -7,7 +7,9 @@
 
 	"github.com/stretchr/testify/require"
 	"google.golang.org/grpc"
+	grpccodes "google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
+	grpcstatus "google.golang.org/grpc/status"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -45,6 +47,18 @@
 	require.EqualError(t, err, "no gitaly_address given")
 }
 
+func TestUnavailableGitalyErr(t *testing.T) {
+	cmd := GitalyCommand{
+		Config:  &config.Config{},
+		Address: "tcp://localhost:9999",
+	}
+
+	expectedErr := grpcstatus.Error(grpccodes.Unavailable, "error")
+	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, expectedErr))
+
+	require.EqualError(t, err, "The git server, Gitaly, is not available at this time. Please contact your administrator.")
+}
+
 func TestRunGitalyCommandMetadata(t *testing.T) {
 	tests := []struct {
 		name string
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -3,7 +3,6 @@
 import (
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log/syslog"
 	"os"
 	"time"
@@ -36,6 +35,7 @@
 		log.WithFormatter(logFmt(cfg.LogFormat)),
 		log.WithOutputName(logFile(cfg.LogFile)),
 		log.WithTimezone(time.UTC),
+		log.WithLogLevel(cfg.LogLevel),
 	}
 }
 
@@ -43,7 +43,7 @@
 // mode an empty LogFile is not accepted and syslog is used as a fallback when LogFile could not be
 // opened for writing.
 func Configure(cfg *config.Config) io.Closer {
-	var closer io.Closer = ioutil.NopCloser(nil)
+	var closer io.Closer = io.NopCloser(nil)
 	err := fmt.Errorf("No logfile specified")
 
 	if cfg.LogFile != "" {
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -1,10 +1,8 @@
 package logger
 
 import (
-	"io/ioutil"
 	"os"
 	"regexp"
-	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -14,7 +12,7 @@
 )
 
 func TestConfigure(t *testing.T) {
-	tmpFile, err := ioutil.TempFile(os.TempDir(), "logtest-")
+	tmpFile, err := os.CreateTemp(os.TempDir(), "logtest-")
 	require.NoError(t, err)
 	defer tmpFile.Close()
 
@@ -27,16 +25,41 @@
 	defer closer.Close()
 
 	log.Info("this is a test")
+	log.WithFields(log.Fields{}).Debug("debug log message")
 
 	tmpFile.Close()
 
-	data, err := ioutil.ReadFile(tmpFile.Name())
+	data, err := os.ReadFile(tmpFile.Name())
+	require.NoError(t, err)
+	require.Contains(t, string(data), `msg":"this is a test"`)
+	require.NotContains(t, string(data), `msg:":"debug log message"`)
+}
+
+func TestConfigureWithDebugLogLevel(t *testing.T) {
+	tmpFile, err := os.CreateTemp(os.TempDir(), "logtest-")
 	require.NoError(t, err)
-	require.True(t, strings.Contains(string(data), `msg":"this is a test"`))
+	defer tmpFile.Close()
+
+	config := config.Config{
+		LogFile:   tmpFile.Name(),
+		LogFormat: "json",
+		LogLevel:  "debug",
+	}
+
+	closer := Configure(&config)
+	defer closer.Close()
+
+	log.WithFields(log.Fields{}).Debug("debug log message")
+
+	tmpFile.Close()
+
+	data, err := os.ReadFile(tmpFile.Name())
+	require.NoError(t, err)
+	require.Contains(t, string(data), `msg":"debug log message"`)
 }
 
 func TestConfigureWithPermissionError(t *testing.T) {
-	tmpPath, err := ioutil.TempDir(os.TempDir(), "logtest-")
+	tmpPath, err := os.MkdirTemp(os.TempDir(), "logtest-")
 	require.NoError(t, err)
 	defer os.RemoveAll(tmpPath)
 
@@ -52,7 +75,7 @@
 }
 
 func TestLogInUTC(t *testing.T) {
-	tmpFile, err := ioutil.TempFile(os.TempDir(), "logtest-")
+	tmpFile, err := os.CreateTemp(os.TempDir(), "logtest-")
 	require.NoError(t, err)
 	defer tmpFile.Close()
 	defer os.Remove(tmpFile.Name())
@@ -67,7 +90,7 @@
 
 	log.Info("this is a test")
 
-	data, err := ioutil.ReadFile(tmpFile.Name())
+	data, err := os.ReadFile(tmpFile.Name())
 	require.NoError(t, err)
 
 	utc := `[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z`
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -29,21 +29,26 @@
 }
 
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+
 	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
 
 	for newChannel := range chans {
+		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
+			ctxlog.Info("connection: handle: unknown channel type")
 			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
 			continue
 		}
 		if !c.concurrentSessions.TryAcquire(1) {
+			ctxlog.Info("connection: handle: too many concurrent sessions")
 			newChannel.Reject(ssh.ResourceShortage, "too many concurrent sessions")
 			metrics.SshdHitMaxSessions.Inc()
 			continue
 		}
 		channel, requests, err := newChannel.Accept()
 		if err != nil {
-			log.WithError(err).Info("could not accept channel")
+			ctxlog.WithError(err).Error("connection: handle: accepting channel failed")
 			c.concurrentSessions.Release(1)
 			continue
 		}
@@ -54,11 +59,12 @@
 			// Prevent a panic in a single session from taking out the whole server
 			defer func() {
 				if err := recover(); err != nil {
-					log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", c.remoteAddr)
+					ctxlog.WithField("recovered_error", err).Warn("panic handling session")
 				}
 			}()
 
 			handler(ctx, channel, requests)
+			ctxlog.Info("connection: handle: done")
 		}()
 	}
 }
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/server_config.go
@@ -0,0 +1,94 @@
+package sshd
+
+import (
+	"context"
+	"encoding/base64"
+	"fmt"
+	"os"
+	"strconv"
+	"time"
+
+	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
+
+	"gitlab.com/gitlab-org/labkit/log"
+)
+
+type serverConfig struct {
+	cfg                  *config.Config
+	hostKeys             []ssh.Signer
+	authorizedKeysClient *authorizedkeys.Client
+}
+
+func newServerConfig(cfg *config.Config) (*serverConfig, error) {
+	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+	}
+
+	var hostKeys []ssh.Signer
+	for _, filename := range cfg.Server.HostKeyFiles {
+		keyRaw, err := os.ReadFile(filename)
+		if err != nil {
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("Failed to read host key")
+			continue
+		}
+		key, err := ssh.ParsePrivateKey(keyRaw)
+		if err != nil {
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("Failed to parse host key")
+			continue
+		}
+
+		hostKeys = append(hostKeys, key)
+	}
+	if len(hostKeys) == 0 {
+		return nil, fmt.Errorf("No host keys could be loaded, aborting")
+	}
+
+	return &serverConfig{cfg: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
+}
+
+func (s *serverConfig) getAuthKey(ctx context.Context, user string, key ssh.PublicKey) (*authorizedkeys.Response, error) {
+	if user != s.cfg.User {
+		return nil, fmt.Errorf("unknown user")
+	}
+	if key.Type() == ssh.KeyAlgoDSA {
+		return nil, fmt.Errorf("DSA is prohibited")
+	}
+
+	ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
+	defer cancel()
+
+	res, err := s.authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
+	if err != nil {
+		return nil, err
+	}
+
+	return res, nil
+}
+
+func (s *serverConfig) get(ctx context.Context) *ssh.ServerConfig {
+	sshCfg := &ssh.ServerConfig{
+		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
+			res, err := s.getAuthKey(ctx, conn.User(), key)
+			if err != nil {
+				return nil, err
+			}
+
+			return &ssh.Permissions{
+				// Record the public key used for authentication.
+				Extensions: map[string]string{
+					"key-id": strconv.FormatInt(res.Id, 10),
+				},
+			}, nil
+		},
+	}
+
+	for _, key := range s.hostKeys {
+		sshCfg.AddHostKey(key)
+	}
+
+	return sshCfg
+}
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/server_config_test.go
@@ -0,0 +1,105 @@
+package sshd
+
+import (
+	"context"
+	"crypto/dsa"
+	"crypto/rand"
+	"crypto/rsa"
+	"path"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+)
+
+func TestNewServerConfigWithoutHosts(t *testing.T) {
+	_, err := newServerConfig(&config.Config{GitlabUrl: "http://localhost"})
+
+	require.Error(t, err)
+	require.Equal(t, "No host keys could be loaded, aborting", err.Error())
+}
+
+func TestFailedAuthorizedKeysClient(t *testing.T) {
+	_, err := newServerConfig(&config.Config{GitlabUrl: "ftp://localhost"})
+
+	require.Error(t, err)
+	require.Equal(t, "failed to initialize GitLab client: Error creating http client: unknown GitLab URL prefix", err.Error())
+}
+
+func TestFailedGetAuthKey(t *testing.T) {
+	testhelper.PrepareTestRootDir(t)
+
+	srvCfg := config.ServerConfig{
+		Listen:                  "127.0.0.1",
+		ConcurrentSessionsLimit: 1,
+		HostKeyFiles: []string{
+			path.Join(testhelper.TestRoot, "certs/valid/server.key"),
+			path.Join(testhelper.TestRoot, "certs/invalid-path.key"),
+			path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+		},
+	}
+
+	cfg, err := newServerConfig(
+		&config.Config{GitlabUrl: "http://localhost", User: "user", Server: srvCfg},
+	)
+	require.NoError(t, err)
+
+	testCases := []struct {
+		desc          string
+		user          string
+		key           ssh.PublicKey
+		expectedError string
+	}{
+		{
+			desc:          "wrong user",
+			user:          "wrong-user",
+			key:           rsaPublicKey(t),
+			expectedError: "unknown user",
+		}, {
+			desc:          "prohibited dsa key",
+			user:          "user",
+			key:           dsaPublicKey(t),
+			expectedError: "DSA is prohibited",
+		}, {
+			desc:          "API error",
+			user:          "user",
+			key:           rsaPublicKey(t),
+			expectedError: "Internal API unreachable",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			_, err = cfg.getAuthKey(context.Background(), tc.user, tc.key)
+			require.Error(t, err)
+			require.Equal(t, tc.expectedError, err.Error())
+		})
+	}
+}
+
+func rsaPublicKey(t *testing.T) ssh.PublicKey {
+	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	require.NoError(t, err)
+
+	publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
+	require.NoError(t, err)
+
+	return publicKey
+}
+
+func dsaPublicKey(t *testing.T) ssh.PublicKey {
+	privateKey := new(dsa.PrivateKey)
+	params := new(dsa.Parameters)
+	require.NoError(t, dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160))
+
+	privateKey.PublicKey.Parameters = *params
+	require.NoError(t, dsa.GenerateKey(privateKey, rand.Reader))
+
+	publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
+	require.NoError(t, err)
+
+	return publicKey
+}
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -2,13 +2,16 @@
 
 import (
 	"context"
+	"errors"
 	"fmt"
+	"reflect"
 
+	"gitlab.com/gitlab-org/labkit/log"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
@@ -39,16 +42,27 @@
 }
 
 func (s *session) handle(ctx context.Context, requests <-chan *ssh.Request) {
+	ctxlog := log.ContextLogger(ctx)
+
+	ctxlog.Debug("session: handle: entering request loop")
+
 	for req := range requests {
+		sessionLog := ctxlog.WithFields(log.Fields{
+			"bytesize":   len(req.Payload),
+			"type":       req.Type,
+			"want_reply": req.WantReply,
+		})
+		sessionLog.Debug("session: handle: request received")
+
 		var shouldContinue bool
 		switch req.Type {
 		case "env":
-			shouldContinue = s.handleEnv(req)
+			shouldContinue = s.handleEnv(ctx, req)
 		case "exec":
 			shouldContinue = s.handleExec(ctx, req)
 		case "shell":
 			shouldContinue = false
-			s.exit(s.handleShell(ctx, req))
+			s.exit(ctx, s.handleShell(ctx, req))
 		default:
 			// Ignore unknown requests but don't terminate the session
 			shouldContinue = true
@@ -57,18 +71,23 @@
 			}
 		}
 
+		sessionLog.WithField("should_continue", shouldContinue).Debug("session: handle: request processed")
+
 		if !shouldContinue {
 			s.channel.Close()
 			break
 		}
 	}
+
+	ctxlog.Debug("session: handle: exiting request loop")
 }
 
-func (s *session) handleEnv(req *ssh.Request) bool {
+func (s *session) handleEnv(ctx context.Context, req *ssh.Request) bool {
 	var accepted bool
 	var envRequest envRequest
 
 	if err := ssh.Unmarshal(req.Payload, &envRequest); err != nil {
+		log.ContextLogger(ctx).WithError(err).Error("session: handleEnv: failed to unmarshal request")
 		return false
 	}
 
@@ -84,6 +103,10 @@
 		req.Reply(accepted, []byte{})
 	}
 
+	log.WithContextFields(
+		ctx, log.Fields{"accepted": accepted, "env_request": envRequest},
+	).Debug("session: handleEnv: processed")
+
 	return true
 }
 
@@ -95,7 +118,8 @@
 
 	s.execCmd = execRequest.Command
 
-	s.exit(s.handleShell(ctx, req))
+	s.exit(ctx, s.handleShell(ctx, req))
+
 	return false
 }
 
@@ -104,19 +128,11 @@
 		req.Reply(true, []byte{})
 	}
 
-	args := &commandargs.Shell{
-		GitlabKeyId: s.gitlabKeyId,
-		Env: sshenv.Env{
-			IsSSHConnection:    true,
-			OriginalCommand:    s.execCmd,
-			GitProtocolVersion: s.gitProtocolVersion,
-			RemoteAddr:         s.remoteAddr,
-		},
-	}
-
-	if err := args.ParseCommand(s.execCmd); err != nil {
-		s.toStderr("Failed to parse command: %v\n", err.Error())
-		return 128
+	env := sshenv.Env{
+		IsSSHConnection:    true,
+		OriginalCommand:    s.execCmd,
+		GitProtocolVersion: s.gitProtocolVersion,
+		RemoteAddr:         s.remoteAddr,
 	}
 
 	rw := &readwriter.ReadWriter{
@@ -125,25 +141,37 @@
 		ErrOut: s.channel.Stderr(),
 	}
 
-	cmd := command.BuildShellCommand(args, s.cfg, rw)
-	if cmd == nil {
-		s.toStderr("Unknown command: %v\n", args.CommandType)
+	cmd, err := shellCmd.NewWithKey(s.gitlabKeyId, env, s.cfg, rw)
+	if err != nil {
+		if !errors.Is(err, disallowedcommand.Error) {
+			s.toStderr(ctx, "Failed to parse command: %v\n", err.Error())
+		}
+		s.toStderr(ctx, "Unknown command: %v\n", s.execCmd)
 		return 128
 	}
 
+	cmdName := reflect.TypeOf(cmd).String()
+	ctxlog := log.ContextLogger(ctx)
+	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("session: handleShell: executing command")
+
 	if err := cmd.Execute(ctx); err != nil {
-		s.toStderr("remote: ERROR: %v\n", err.Error())
+		s.toStderr(ctx, "remote: ERROR: %v\n", err.Error())
 		return 1
 	}
 
+	ctxlog.Info("session: handleShell: command executed successfully")
+
 	return 0
 }
 
-func (s *session) toStderr(format string, args ...interface{}) {
-	fmt.Fprintf(s.channel.Stderr(), format, args...)
+func (s *session) toStderr(ctx context.Context, format string, args ...interface{}) {
+	out := fmt.Sprintf(format, args...)
+	log.WithContextFields(ctx, log.Fields{"stderr": out}).Debug("session: toStderr: output")
+	fmt.Fprint(s.channel.Stderr(), out)
 }
 
-func (s *session) exit(status uint32) {
+func (s *session) exit(ctx context.Context, status uint32) {
+	log.WithContextFields(ctx, log.Fields{"exit_status": status}).Info("session: exit: exiting")
 	req := exitStatusReq{ExitStatus: status}
 
 	s.channel.CloseWrite()
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
new file mode 100644
--- /dev/null
+++ b/internal/sshd/session_test.go
@@ -0,0 +1,189 @@
+package sshd
+
+import (
+	"bytes"
+	"context"
+	"io"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+)
+
+type fakeChannel struct {
+	stdErr             io.ReadWriter
+	sentRequestName    string
+	sentRequestPayload []byte
+}
+
+func (f *fakeChannel) Read(data []byte) (int, error) {
+	return 0, nil
+}
+
+func (f *fakeChannel) Write(data []byte) (int, error) {
+	return 0, nil
+}
+
+func (f *fakeChannel) Close() error {
+	return nil
+}
+
+func (f *fakeChannel) CloseWrite() error {
+	return nil
+}
+
+func (f *fakeChannel) SendRequest(name string, wantReply bool, payload []byte) (bool, error) {
+	f.sentRequestName = name
+	f.sentRequestPayload = payload
+
+	return true, nil
+}
+
+func (f *fakeChannel) Stderr() io.ReadWriter {
+	return f.stdErr
+}
+
+var requests = []testserver.TestRequestHandler{
+	{
+		Path: "/api/v4/internal/discover",
+		Handler: func(w http.ResponseWriter, r *http.Request) {
+			w.Write([]byte(`{"id": 1000, "name": "Test User", "username": "test-user"}`))
+		},
+	},
+}
+
+func TestHandleEnv(t *testing.T) {
+	testCases := []struct {
+		desc                    string
+		payload                 []byte
+		expectedProtocolVersion string
+		expectedResult          bool
+	}{
+		{
+			desc:                    "invalid payload",
+			payload:                 []byte("invalid"),
+			expectedProtocolVersion: "1",
+			expectedResult:          false,
+		}, {
+			desc:                    "valid payload",
+			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL", Value: "2"}),
+			expectedProtocolVersion: "2",
+			expectedResult:          true,
+		}, {
+			desc:                    "valid payload with forbidden env var",
+			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL_ENV", Value: "2"}),
+			expectedProtocolVersion: "1",
+			expectedResult:          true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			s := &session{gitProtocolVersion: "1"}
+			r := &ssh.Request{Payload: tc.payload}
+
+			require.Equal(t, s.handleEnv(context.Background(), r), tc.expectedResult)
+			require.Equal(t, s.gitProtocolVersion, tc.expectedProtocolVersion)
+		})
+	}
+}
+
+func TestHandleExec(t *testing.T) {
+	testCases := []struct {
+		desc               string
+		payload            []byte
+		expectedExecCmd    string
+		sentRequestName    string
+		sentRequestPayload []byte
+	}{
+		{
+			desc:            "invalid payload",
+			payload:         []byte("invalid"),
+			expectedExecCmd: "",
+			sentRequestName: "",
+		}, {
+			desc:               "valid payload",
+			payload:            ssh.Marshal(execRequest{Command: "discover"}),
+			expectedExecCmd:    "discover",
+			sentRequestName:    "exit-status",
+			sentRequestPayload: ssh.Marshal(exitStatusReq{ExitStatus: 0}),
+		},
+	}
+
+	url := testserver.StartHttpServer(t, requests)
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			out := &bytes.Buffer{}
+			f := &fakeChannel{stdErr: out}
+			s := &session{
+				gitlabKeyId: "root",
+				channel:     f,
+				cfg:         &config.Config{GitlabUrl: url},
+			}
+			r := &ssh.Request{Payload: tc.payload}
+
+			require.Equal(t, false, s.handleExec(context.Background(), r))
+			require.Equal(t, tc.sentRequestName, f.sentRequestName)
+			require.Equal(t, tc.sentRequestPayload, f.sentRequestPayload)
+		})
+	}
+}
+
+func TestHandleShell(t *testing.T) {
+	testCases := []struct {
+		desc             string
+		cmd              string
+		errMsg           string
+		gitlabKeyId      string
+		expectedExitCode uint32
+	}{
+		{
+			desc:             "fails to parse command",
+			cmd:              `\`,
+			errMsg:           "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
+			gitlabKeyId:      "root",
+			expectedExitCode: 128,
+		}, {
+			desc:             "specified command is unknown",
+			cmd:              "unknown-command",
+			errMsg:           "Unknown command: unknown-command\n",
+			gitlabKeyId:      "root",
+			expectedExitCode: 128,
+		}, {
+			desc:             "fails to parse command",
+			cmd:              "discover",
+			gitlabKeyId:      "",
+			errMsg:           "remote: ERROR: Failed to get username: who='' is invalid\n",
+			expectedExitCode: 1,
+		}, {
+			desc:             "fails to parse command",
+			cmd:              "discover",
+			errMsg:           "",
+			gitlabKeyId:      "root",
+			expectedExitCode: 0,
+		},
+	}
+
+	url := testserver.StartHttpServer(t, requests)
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			out := &bytes.Buffer{}
+			s := &session{
+				gitlabKeyId: tc.gitlabKeyId,
+				execCmd:     tc.cmd,
+				channel:     &fakeChannel{stdErr: out},
+				cfg:         &config.Config{GitlabUrl: url},
+			}
+			r := &ssh.Request{}
+
+			require.Equal(t, tc.expectedExitCode, s.handleShell(context.Background(), r))
+			require.Equal(t, tc.errMsg, out.String())
+		})
+	}
+}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -2,13 +2,9 @@
 
 import (
 	"context"
-	"encoding/base64"
-	"errors"
 	"fmt"
-	"io/ioutil"
 	"net"
 	"net/http"
-	"strconv"
 	"sync"
 	"time"
 
@@ -16,7 +12,6 @@
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/log"
@@ -35,44 +30,24 @@
 type Server struct {
 	Config *config.Config
 
-	status               status
-	statusMu             sync.Mutex
-	wg                   sync.WaitGroup
-	listener             net.Listener
-	hostKeys             []ssh.Signer
-	authorizedKeysClient *authorizedkeys.Client
+	status       status
+	statusMu     sync.Mutex
+	wg           sync.WaitGroup
+	listener     net.Listener
+	serverConfig *serverConfig
 }
 
 func NewServer(cfg *config.Config) (*Server, error) {
-	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
+	serverConfig, err := newServerConfig(cfg)
 	if err != nil {
-		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+		return nil, err
 	}
 
-	var hostKeys []ssh.Signer
-	for _, filename := range cfg.Server.HostKeyFiles {
-		keyRaw, err := ioutil.ReadFile(filename)
-		if err != nil {
-			log.WithError(err).Warnf("Failed to read host key %v", filename)
-			continue
-		}
-		key, err := ssh.ParsePrivateKey(keyRaw)
-		if err != nil {
-			log.WithError(err).Warnf("Failed to parse host key %v", filename)
-			continue
-		}
-
-		hostKeys = append(hostKeys, key)
-	}
-	if len(hostKeys) == 0 {
-		return nil, fmt.Errorf("No host keys could be loaded, aborting")
-	}
-
-	return &Server{Config: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
+	return &Server{Config: cfg, serverConfig: serverConfig}, nil
 }
 
 func (s *Server) ListenAndServe(ctx context.Context) error {
-	if err := s.listen(); err != nil {
+	if err := s.listen(ctx); err != nil {
 		return err
 	}
 	defer s.listener.Close()
@@ -110,7 +85,7 @@
 	return mux
 }
 
-func (s *Server) listen() error {
+func (s *Server) listen(ctx context.Context) error {
 	sshListener, err := net.Listen("tcp", s.Config.Server.Listen)
 	if err != nil {
 		return fmt.Errorf("failed to listen for connection: %w", err)
@@ -119,13 +94,14 @@
 	if s.Config.Server.ProxyProtocol {
 		sshListener = &proxyproto.Listener{
 			Listener:          sshListener,
+			Policy:            unconditionalRequirePolicy,
 			ReadHeaderTimeout: ProxyHeaderTimeout,
 		}
 
-		log.Info("Proxy protocol is enabled")
+		log.ContextLogger(ctx).Info("Proxy protocol is enabled")
 	}
 
-	log.WithFields(log.Fields{"tcp_address": sshListener.Addr().String()}).Info("Listening for SSH connections")
+	log.WithContextFields(ctx, log.Fields{"tcp_address": sshListener.Addr().String()}).Info("Listening for SSH connections")
 
 	s.listener = sshListener
 
@@ -142,7 +118,7 @@
 				break
 			}
 
-			log.WithError(err).Warn("Failed to accept connection")
+			log.ContextLogger(ctx).WithError(err).Warn("Failed to accept connection")
 			continue
 		}
 
@@ -168,57 +144,29 @@
 	return s.status
 }
 
-func (s *Server) serverConfig(ctx context.Context) *ssh.ServerConfig {
-	sshCfg := &ssh.ServerConfig{
-		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
-			if conn.User() != s.Config.User {
-				return nil, errors.New("unknown user")
-			}
-			if key.Type() == ssh.KeyAlgoDSA {
-				return nil, errors.New("DSA is prohibited")
-			}
-			ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
-			defer cancel()
-			res, err := s.authorizedKeysClient.GetByKey(ctx, base64.RawStdEncoding.EncodeToString(key.Marshal()))
-			if err != nil {
-				return nil, err
-			}
-
-			return &ssh.Permissions{
-				// Record the public key used for authentication.
-				Extensions: map[string]string{
-					"key-id": strconv.FormatInt(res.Id, 10),
-				},
-			}, nil
-		},
-	}
-
-	for _, key := range s.hostKeys {
-		sshCfg.AddHostKey(key)
-	}
-
-	return sshCfg
-}
-
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
 	remoteAddr := nconn.RemoteAddr().String()
 
 	defer s.wg.Done()
 	defer nconn.Close()
 
+	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
+	defer cancel()
+
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": remoteAddr})
+
 	// Prevent a panic in a single connection from taking out the whole server
 	defer func() {
 		if err := recover(); err != nil {
-			log.WithFields(log.Fields{"recovered_error": err}).Warnf("panic handling session from %s", remoteAddr)
+			ctxlog.Warn("panic handling session")
 		}
 	}()
 
-	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
-	defer cancel()
+	ctxlog.Info("server: handleConn: start")
 
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig(ctx))
+	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
-		log.WithError(err).Info("Failed to initialize SSH connection")
+		ctxlog.WithError(err).Error("server: handleConn: failed to initialize SSH connection")
 		return
 	}
 
@@ -235,4 +183,10 @@
 
 		session.handle(ctx, requests)
 	})
+
+	ctxlog.Info("server: handleConn: done")
 }
+
+func unconditionalRequirePolicy(_ net.Addr) (proxyproto.Policy, error) {
+	return proxyproto.REQUIRE, nil
+}
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -3,9 +3,9 @@
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
+	"os"
 	"path"
 	"testing"
 	"time"
@@ -47,6 +47,18 @@
 	verifyStatus(t, s, StatusClosed)
 }
 
+func TestListenAndServeRejectsPlainConnectionsWhenProxyProtocolEnabled(t *testing.T) {
+	setupServerWithProxyProtocolEnabled(t)
+
+	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
+	if client != nil {
+		client.Close()
+	}
+
+	require.Error(t, err, "Expected plain SSH request to be failed")
+	require.Regexp(t, "ssh: handshake failed", err.Error())
+}
+
 func TestCorrelationId(t *testing.T) {
 	setupServer(t)
 
@@ -104,9 +116,39 @@
 	require.Equal(t, 200, r.Result().StatusCode)
 }
 
+func TestInvalidClientConfig(t *testing.T) {
+	setupServer(t)
+
+	cfg := clientConfig(t)
+	cfg.User = "unknown"
+	_, err := ssh.Dial("tcp", serverUrl, cfg)
+	require.Error(t, err)
+}
+
+func TestInvalidServerConfig(t *testing.T) {
+	s := &Server{Config: &config.Config{Server: config.ServerConfig{Listen: "invalid"}}}
+	err := s.ListenAndServe(context.Background())
+
+	require.Error(t, err)
+	require.Equal(t, "failed to listen for connection: listen tcp: address invalid: missing port in address", err.Error())
+	require.Nil(t, s.Shutdown())
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
+	return setupServerWithConfig(t, nil)
+}
+
+func setupServerWithProxyProtocolEnabled(t *testing.T) *Server {
+	t.Helper()
+
+	return setupServerWithConfig(t, &config.Config{Server: config.ServerConfig{ProxyProtocol: true}})
+}
+
+func setupServerWithConfig(t *testing.T, cfg *config.Config) *Server {
+	t.Helper()
+
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/authorized_keys",
@@ -130,13 +172,20 @@
 	testhelper.PrepareTestRootDir(t)
 
 	url := testserver.StartSocketHttpServer(t, requests)
-	srvCfg := config.ServerConfig{
-		Listen:                  serverUrl,
-		ConcurrentSessionsLimit: 1,
-		HostKeyFiles:            []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")},
+
+	if cfg == nil {
+		cfg = &config.Config{}
 	}
 
-	s, err := NewServer(&config.Config{User: user, RootDir: "/tmp", GitlabUrl: url, Server: srvCfg})
+	// All things that don't need to be configurable in tests yet
+	cfg.GitlabUrl = url
+	cfg.RootDir = "/tmp"
+	cfg.User = user
+	cfg.Server.Listen = serverUrl
+	cfg.Server.ConcurrentSessionsLimit = 1
+	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
+
+	s, err := NewServer(cfg)
 	require.NoError(t, err)
 
 	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
@@ -148,11 +197,11 @@
 }
 
 func clientConfig(t *testing.T) *ssh.ClientConfig {
-	keyRaw, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server_authorized_key"))
+	keyRaw, err := os.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server_authorized_key"))
 	pKey, _, _, _, err := ssh.ParseAuthorizedKey(keyRaw)
 	require.NoError(t, err)
 
-	key, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "certs/client/key.pem"))
+	key, err := os.ReadFile(path.Join(testhelper.TestRoot, "certs/client/key.pem"))
 	require.NoError(t, err)
 	signer, err := ssh.ParsePrivateKey(key)
 	require.NoError(t, err)
@@ -177,14 +226,5 @@
 }
 
 func verifyStatus(t *testing.T, s *Server, st status) {
-	for i := 5; i < 500; i += 50 {
-		if s.getStatus() == st {
-			break
-		}
-
-		// Sleep incrementally ~2s in total
-		time.Sleep(time.Duration(i) * time.Millisecond)
-	}
-
-	require.Equal(t, st, s.getStatus())
+	require.Eventually(t, func() bool { return s.getStatus() == st }, 2*time.Second, time.Millisecond)
 }
diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go
--- a/internal/testhelper/testhelper.go
+++ b/internal/testhelper/testhelper.go
@@ -2,7 +2,6 @@
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path"
 	"runtime"
@@ -13,7 +12,7 @@
 )
 
 var (
-	TestRoot, _ = ioutil.TempDir("", "test-gitlab-shell")
+	TestRoot, _ = os.MkdirTemp("", "test-gitlab-shell")
 )
 
 func TempEnv(env map[string]string) func() {
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1645871769 -3600
#      Sat Feb 26 11:36:09 2022 +0100
# Branch heptapod-stable
# Node ID 9f8076c43eab66b8867a86e136f915048237a657
# Parent  eed45a48a4bd15aa1f57138e286080b4aaddec37
# Parent  0933769f90885c8c0ef6db40d1aeeea758a7632d
heptapod#631: making Heptapod 0.29 the new stable

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.4
-golang 1.16.10
+golang 1.16.12
# HG changeset patch
# User Will Chandler <wchandler@gitlab.com>
# Date 1640197821 18000
#      Wed Dec 22 13:30:21 2021 -0500
# Node ID 5d3302f022b92a5489a46b4fdb86dbc8bc5d043a
# Parent  0933769f90885c8c0ef6db40d1aeeea758a7632d
Send full git request/response in SSHD tests

Before 9deaf47f1ecb00f0f36d18ee4a0fb1576f5a0efe, Gitaly would return
success for `SSHUploadPack` and `SSHUploadArchive` regardless of the
exit code of the `git upload-pack|archive` process. As a result, the
gitlab-sshd acceptance tests could rely on no errors being returned from
Gitaly.

Currently these tests send the minimum request needed to start a
session, causing the server git process to fail as the `0000` flush
packet to end the session is never sent.

This commit fixes the tests by sending the full request/response needed
for a successful git operation.

diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -408,18 +408,34 @@
 	ensureGitalyRepository(t)
 
 	client := runSSHD(t, successAPI(t))
-
 	session, err := client.NewSession()
 	require.NoError(t, err)
 	defer session.Close()
 
-	output, err := session.Output(fmt.Sprintf("git-upload-pack %s", testRepo))
+	stdin, err := session.StdinPipe()
+	require.NoError(t, err)
+
+	stdout, err := session.StdoutPipe()
+	require.NoError(t, err)
+
+	reader := bufio.NewReader(stdout)
+
+	err = session.Start(fmt.Sprintf("git-upload-pack %s", testRepo))
+	require.NoError(t, err)
+
+	line, err := reader.ReadString('\n')
+	require.NoError(t, err)
+	require.Regexp(t, "^[0-9a-f]{44} HEAD.+", line)
+
+	// Gracefully close connection
+	_, err = fmt.Fprintln(stdin, "0000")
+	require.NoError(t, err)
+
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 
 	outputLines := strings.Split(string(output), "\n")
 
-	require.Regexp(t, "^[0-9a-f]{44} HEAD.+", outputLines[0])
-
 	for i := 1; i < (len(outputLines) - 1); i++ {
 		require.Regexp(t, "^[0-9a-f]{44} refs/(heads|tags)/[^ ]+", outputLines[i])
 	}
@@ -436,11 +452,29 @@
 	require.NoError(t, err)
 	defer session.Close()
 
-	output, err := session.Output(fmt.Sprintf("git-upload-archive %s", testRepo))
+	stdin, err := session.StdinPipe()
+	require.NoError(t, err)
+
+	stdout, err := session.StdoutPipe()
+	require.NoError(t, err)
+
+	reader := bufio.NewReader(stdout)
+
+	err = session.Start(fmt.Sprintf("git-upload-archive %s", testRepo))
 	require.NoError(t, err)
 
-	outputLines := strings.Split(string(output), "\n")
+	_, err = fmt.Fprintln(stdin, "0012argument HEAD\n0000")
+
+	line, err := reader.ReadString('\n')
+	require.Equal(t, "0008ACK\n", line)
+	require.NoError(t, err)
 
-	require.Equal(t, "0008ACK", outputLines[0])
-	require.Regexp(t, "^0000", outputLines[1])
+	// Gracefully close connection
+	_, err = fmt.Fprintln(stdin, "0000")
+	require.NoError(t, err)
+
+	output, err := io.ReadAll(stdout)
+	require.NoError(t, err)
+
+	require.Equal(t, []byte("0000"), output[len(output)-4:])
 }
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1640686192 0
#      Tue Dec 28 10:09:52 2021 +0000
# Node ID ef3162790f053c321552def3806ea2ce4047c998
# Parent  0933769f90885c8c0ef6db40d1aeeea758a7632d
# Parent  5d3302f022b92a5489a46b4fdb86dbc8bc5d043a
Merge branch 'wc-sshd-upload-pack' into 'main'

Send full git request/response in SSHD tests

See merge request gitlab-org/gitlab-shell!550

diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -408,18 +408,34 @@
 	ensureGitalyRepository(t)
 
 	client := runSSHD(t, successAPI(t))
-
 	session, err := client.NewSession()
 	require.NoError(t, err)
 	defer session.Close()
 
-	output, err := session.Output(fmt.Sprintf("git-upload-pack %s", testRepo))
+	stdin, err := session.StdinPipe()
+	require.NoError(t, err)
+
+	stdout, err := session.StdoutPipe()
+	require.NoError(t, err)
+
+	reader := bufio.NewReader(stdout)
+
+	err = session.Start(fmt.Sprintf("git-upload-pack %s", testRepo))
+	require.NoError(t, err)
+
+	line, err := reader.ReadString('\n')
+	require.NoError(t, err)
+	require.Regexp(t, "^[0-9a-f]{44} HEAD.+", line)
+
+	// Gracefully close connection
+	_, err = fmt.Fprintln(stdin, "0000")
+	require.NoError(t, err)
+
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 
 	outputLines := strings.Split(string(output), "\n")
 
-	require.Regexp(t, "^[0-9a-f]{44} HEAD.+", outputLines[0])
-
 	for i := 1; i < (len(outputLines) - 1); i++ {
 		require.Regexp(t, "^[0-9a-f]{44} refs/(heads|tags)/[^ ]+", outputLines[i])
 	}
@@ -436,11 +452,29 @@
 	require.NoError(t, err)
 	defer session.Close()
 
-	output, err := session.Output(fmt.Sprintf("git-upload-archive %s", testRepo))
+	stdin, err := session.StdinPipe()
+	require.NoError(t, err)
+
+	stdout, err := session.StdoutPipe()
+	require.NoError(t, err)
+
+	reader := bufio.NewReader(stdout)
+
+	err = session.Start(fmt.Sprintf("git-upload-archive %s", testRepo))
 	require.NoError(t, err)
 
-	outputLines := strings.Split(string(output), "\n")
+	_, err = fmt.Fprintln(stdin, "0012argument HEAD\n0000")
+
+	line, err := reader.ReadString('\n')
+	require.Equal(t, "0008ACK\n", line)
+	require.NoError(t, err)
 
-	require.Equal(t, "0008ACK", outputLines[0])
-	require.Regexp(t, "^0000", outputLines[1])
+	// Gracefully close connection
+	_, err = fmt.Fprintln(stdin, "0000")
+	require.NoError(t, err)
+
+	output, err := io.ReadAll(stdout)
+	require.NoError(t, err)
+
+	require.Equal(t, []byte("0000"), output[len(output)-4:])
 }
# HG changeset patch
# User Will Chandler <wchandler@gitlab.com>
# Date 1639148107 18000
#      Fri Dec 10 09:55:07 2021 -0500
# Node ID 81f0009bfceba7a72673b0e55c8a0570ebb5c244
# Parent  ef3162790f053c321552def3806ea2ce4047c998
Suppress internal errors in client output

Until recently, Gitaly was silently swallowing any errors returned by
SSH `git upload-pack` processes. Clients would still receive stderr
output and a non-zero return code, but Gitlab-Shell would receive error
as nil and log success.

With 9deaf47f1ecb00f0f36d18ee4a0fb1576f5a0efe Gitaly will now return an
error when git fails, but this causes Gitlab-Shell to print out the
GRPC error code as a message to the client:

> fatal: couldn't find remote ref not-a-real-ref
> fatal: the remote end hung up unexpectedly
> remote:
> remote:
> ========================================================================
> remote:
> remote: rpc error: code = Internal desc = SSHUploadPack: exit status 128
> remote:
> remote:
> ========================================================================
> remote:

The `remote:` text gives no additional context for the user and adds
clutter.

This commit suppresses the additional message added by Gitlab-Shell on
failure when the error type is `Internal`, returning client output to
the format it was prior to the Gitaly change.

diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -5,6 +5,9 @@
 	"os"
 	"reflect"
 
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
+
 	"gitlab.com/gitlab-org/labkit/log"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
@@ -71,7 +74,9 @@
 
 	if err := cmd.Execute(ctx); err != nil {
 		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
-		console.DisplayWarningMessage(err.Error(), readWriter.ErrOut)
+		if grpcstatus.Convert(err).Code() != grpccodes.Internal {
+			console.DisplayWarningMessage(err.Error(), readWriter.ErrOut)
+		}
 		os.Exit(1)
 	}
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1641380482 0
#      Wed Jan 05 11:01:22 2022 +0000
# Node ID 81285a3e83664a7906796d7ddc887879aa266320
# Parent  ef3162790f053c321552def3806ea2ce4047c998
# Parent  81f0009bfceba7a72673b0e55c8a0570ebb5c244
Merge branch 'wc-intern-err' into 'main'

Suppress internal errors in client output

See merge request gitlab-org/gitlab-shell!549

diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -5,6 +5,9 @@
 	"os"
 	"reflect"
 
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
+
 	"gitlab.com/gitlab-org/labkit/log"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
@@ -71,7 +74,9 @@
 
 	if err := cmd.Execute(ctx); err != nil {
 		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
-		console.DisplayWarningMessage(err.Error(), readWriter.ErrOut)
+		if grpcstatus.Convert(err).Code() != grpccodes.Internal {
+			console.DisplayWarningMessage(err.Error(), readWriter.ErrOut)
+		}
 		os.Exit(1)
 	}
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1641996918 -10800
#      Wed Jan 12 17:15:18 2022 +0300
# Node ID 5505522b8cd49fb6723d228adb4f16209e1f2cdb
# Parent  81285a3e83664a7906796d7ddc887879aa266320
Deprecate self_signed_cert config setting

The option isn't required to accept self-signed certs

On the other hand, if the option set to true it makes
machine-in-the-middle attack possible

Let's clarify it in the code that the option is deprecated

diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -162,7 +162,10 @@
 		}
 	}
 	tlsConfig := &tls.Config{
-		RootCAs:            certPool,
+		RootCAs: certPool,
+		// The self_signed_cert config setting is deprecated
+		// The field and its usage is going to be removed in
+		// https://gitlab.com/gitlab-org/gitlab-shell/-/issues/541
 		InsecureSkipVerify: selfSignedCert,
 		MinVersion:         tls.VersionTLS12,
 	}
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -26,6 +26,11 @@
 #  password: somepass
 #  ca_file: /etc/ssl/cert.pem
 #  ca_path: /etc/pki/tls/certs
+#
+#  The self_signed_cert option is deprecated
+#  When it's set to true, any certificate is accepted, which may make machine-in-the-middle attack possible
+#  Certificates specified in ca_file and ca_path are trusted anyway even if they are self-signed
+#  Issue: https://gitlab.com/gitlab-org/gitlab-shell/-/issues/120
   self_signed_cert: false
 
 # File used as authorized_keys for gitlab user
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1642039980 0
#      Thu Jan 13 02:13:00 2022 +0000
# Node ID e5c80a0bed6036b81ccec2e4830b038bf146378e
# Parent  81285a3e83664a7906796d7ddc887879aa266320
# Parent  5505522b8cd49fb6723d228adb4f16209e1f2cdb
Merge branch 'id-deprecate-self-signed-cert' into 'main'

Deprecate self_signed_cert config setting

See merge request gitlab-org/gitlab-shell!552

diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -162,7 +162,10 @@
 		}
 	}
 	tlsConfig := &tls.Config{
-		RootCAs:            certPool,
+		RootCAs: certPool,
+		// The self_signed_cert config setting is deprecated
+		// The field and its usage is going to be removed in
+		// https://gitlab.com/gitlab-org/gitlab-shell/-/issues/541
 		InsecureSkipVerify: selfSignedCert,
 		MinVersion:         tls.VersionTLS12,
 	}
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -26,6 +26,11 @@
 #  password: somepass
 #  ca_file: /etc/ssl/cert.pem
 #  ca_path: /etc/pki/tls/certs
+#
+#  The self_signed_cert option is deprecated
+#  When it's set to true, any certificate is accepted, which may make machine-in-the-middle attack possible
+#  Certificates specified in ca_file and ca_path are trusted anyway even if they are self-signed
+#  Issue: https://gitlab.com/gitlab-org/gitlab-shell/-/issues/120
   self_signed_cert: false
 
 # File used as authorized_keys for gitlab user
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1642140271 28800
#      Thu Jan 13 22:04:31 2022 -0800
# Node ID c8a79b1dffb2423ced8c3ce652770108d9632351
# Parent  e5c80a0bed6036b81ccec2e4830b038bf146378e
Update to Ruby 2.7.5

We don't need Ruby 2.7.4 cluttering our GDK when everything else has
been upgraded to 2.7.5.

diff --git a/.ruby-version b/.ruby-version
--- a/.ruby-version
+++ b/.ruby-version
@@ -1,1 +1,1 @@
-2.7.4
+2.7.5
diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
-ruby 2.7.4
+ruby 2.7.5
 golang 1.16.12
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1642140688 0
#      Fri Jan 14 06:11:28 2022 +0000
# Node ID 10536301a797522c34722e346ccafe1bb47ca5cc
# Parent  e5c80a0bed6036b81ccec2e4830b038bf146378e
# Parent  c8a79b1dffb2423ced8c3ce652770108d9632351
Merge branch 'sh-update-ruby-2.7.5' into 'main'

Update to Ruby 2.7.5

See merge request gitlab-org/gitlab-shell!553

diff --git a/.ruby-version b/.ruby-version
--- a/.ruby-version
+++ b/.ruby-version
@@ -1,1 +1,1 @@
-2.7.4
+2.7.5
diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
-ruby 2.7.4
+ruby 2.7.5
 golang 1.16.12
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1642375469 0
#      Sun Jan 16 23:24:29 2022 +0000
# Node ID 4a3f2772fe135c496754bdf2250a9fc901213428
# Parent  10536301a797522c34722e346ccafe1bb47ca5cc
Release v13.22.2

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,11 @@
+v13.22.2
+
+- Update to Ruby 2.7.5 !553
+- Deprecate self_signed_cert config setting !552
+- Send full git request/response in SSHD tests !550
+- Suppress internal errors in client output !549
+- Bump .tool_versions to use Go v1.16.12 !548
+
 v13.22.1
 
 - Remove SSL_CERT_DIR logging !546
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.22.1
+13.22.2
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1642375470 0
#      Sun Jan 16 23:24:30 2022 +0000
# Node ID 35c1c2407c4cda8a59d29d847e460c3f7067b267
# Parent  10536301a797522c34722e346ccafe1bb47ca5cc
# Parent  4a3f2772fe135c496754bdf2250a9fc901213428
Merge branch 'sh-release-13.22.2' into 'main'

Release v13.22.2

See merge request gitlab-org/gitlab-shell!555

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,11 @@
+v13.22.2
+
+- Update to Ruby 2.7.5 !553
+- Deprecate self_signed_cert config setting !552
+- Send full git request/response in SSHD tests !550
+- Suppress internal errors in client output !549
+- Bump .tool_versions to use Go v1.16.12 !548
+
 v13.22.1
 
 - Remove SSL_CERT_DIR logging !546
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.22.1
+13.22.2
# HG changeset patch
# User Sean Carroll <scarroll@gitlab.com>
# Date 1642588343 0
#      Wed Jan 19 10:32:23 2022 +0000
# Node ID 197a236b521411a7a19204fd5470caad67c3e036
# Parent  35c1c2407c4cda8a59d29d847e460c3f7067b267
Rate limiting documentation

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -94,7 +94,7 @@
 tests requiring Gitaly will be skipped. They will always run in the CI
 environment.
 
-## Git LFS remark
+## Git LFS
 
 Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
 
@@ -113,6 +113,16 @@
 - Logging too much is better than not logging enough. If a message seems too
   verbose, consider reducing the log level before removing the message.
 
+## Rate Limiting
+
+GitLab Shell performs rate-limiting by user account and project for git operations. GitLab Shell accepts git operation requests and then makes a call to the Rails rate-limiter (backed by Redis). If the `user + project` exceeds the rate limit then GitLab Shell will then drop further connection requests for that `user + project`.
+
+The rate-limiter is applied at the git command (plumbing) level. Each command has a rate limit of 600/minute. For example, `git push` has 600/minute and `git pull` has another 600/minute. 
+
+Because they are using the same plumbing command `git-upload-pack`, `git pull` and `git clone` are in effect the same command for the purposes of rate-limiting.
+
+There is also a rate-limiter in place in Gitaly, but the calls will never be made to Gitaly if the rate limit is exceeded in Gitlab Shell (Rails).
+
 ## Releasing
 
 See [PROCESS.md](./PROCESS.md)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1642588344 0
#      Wed Jan 19 10:32:24 2022 +0000
# Node ID 72999fcfa91c17621e5aa8d7c5b1a0d5397c9534
# Parent  35c1c2407c4cda8a59d29d847e460c3f7067b267
# Parent  197a236b521411a7a19204fd5470caad67c3e036
Merge branch 'rate-limiting-docs' into 'main'

Rate limiting documentation

See merge request gitlab-org/gitlab-shell!556

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -94,7 +94,7 @@
 tests requiring Gitaly will be skipped. They will always run in the CI
 environment.
 
-## Git LFS remark
+## Git LFS
 
 Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
 
@@ -113,6 +113,16 @@
 - Logging too much is better than not logging enough. If a message seems too
   verbose, consider reducing the log level before removing the message.
 
+## Rate Limiting
+
+GitLab Shell performs rate-limiting by user account and project for git operations. GitLab Shell accepts git operation requests and then makes a call to the Rails rate-limiter (backed by Redis). If the `user + project` exceeds the rate limit then GitLab Shell will then drop further connection requests for that `user + project`.
+
+The rate-limiter is applied at the git command (plumbing) level. Each command has a rate limit of 600/minute. For example, `git push` has 600/minute and `git pull` has another 600/minute. 
+
+Because they are using the same plumbing command `git-upload-pack`, `git pull` and `git clone` are in effect the same command for the purposes of rate-limiting.
+
+There is also a rate-limiter in place in Gitaly, but the calls will never be made to Gitaly if the rate limit is exceeded in Gitlab Shell (Rails).
+
 ## Releasing
 
 See [PROCESS.md](./PROCESS.md)
# HG changeset patch
# User Jacob Vosmaer <jacob@gitlab.com>
# Date 1642692988 0
#      Thu Jan 20 15:36:28 2022 +0000
# Node ID 98fea8ab6995c4f9b3d8a13cd5e43847ce23f515
# Parent  e5c80a0bed6036b81ccec2e4830b038bf146378e
Refactor client response tests

This reduces coupling between tests in
internal/gitlabnet/accessverifier/client_test.go, and will make it
easier to add new test cases in the future.

Note that the test server had a special behavior for the username
"second", but this was never used. So we removed that behavior in this
commit.

diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -11,7 +11,6 @@
 
 	"github.com/stretchr/testify/require"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -54,7 +53,11 @@
 }
 
 func TestSuccessfulResponses(t *testing.T) {
-	client := setup(t, "")
+	okResponse := testResponse{body: responseBody(t, "allowed.json"), status: http.StatusOK}
+	client := setup(t,
+		map[string]testResponse{"first": okResponse},
+		map[string]testResponse{"1": okResponse},
+	)
 
 	testCases := []struct {
 		desc string
@@ -84,7 +87,12 @@
 }
 
 func TestGeoPushGetCustomAction(t *testing.T) {
-	client := setup(t, "responses/allowed_with_push_payload.json")
+	client := setup(t, map[string]testResponse{
+		"custom": {
+			body:   responseBody(t, "allowed_with_push_payload.json"),
+			status: 300,
+		},
+	}, nil)
 
 	args := &commandargs.Shell{GitlabUsername: "custom"}
 	result, err := client.Verify(context.Background(), args, receivePackAction, repo)
@@ -106,7 +114,12 @@
 }
 
 func TestGeoPullGetCustomAction(t *testing.T) {
-	client := setup(t, "responses/allowed_with_pull_payload.json")
+	client := setup(t, map[string]testResponse{
+		"custom": {
+			body:   responseBody(t, "allowed_with_pull_payload.json"),
+			status: 300,
+		},
+	}, nil)
 
 	args := &commandargs.Shell{GitlabUsername: "custom"}
 	result, err := client.Verify(context.Background(), args, uploadPackAction, repo)
@@ -128,7 +141,11 @@
 }
 
 func TestErrorResponses(t *testing.T) {
-	client := setup(t, "")
+	client := setup(t, nil, map[string]testResponse{
+		"2": {body: []byte(`{"message":"Not allowed!"}`), status: http.StatusForbidden},
+		"3": {body: []byte(`{"message":"broken json!`), status: http.StatusOK},
+		"4": {status: http.StatusForbidden},
+	})
 
 	testCases := []struct {
 		desc          string
@@ -163,20 +180,21 @@
 	}
 }
 
-func setup(t *testing.T, allowedPayload string) *Client {
-	testhelper.PrepareTestRootDir(t)
-
-	body, err := os.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
-	require.NoError(t, err)
+type testResponse struct {
+	body   []byte
+	status int
+}
 
-	var bodyWithPayload []byte
+func responseBody(t *testing.T, name string) []byte {
+	t.Helper()
+	testhelper.PrepareTestRootDir(t)
+	body, err := os.ReadFile(path.Join(testhelper.TestRoot, "responses", name))
+	require.NoError(t, err)
+	return body
+}
 
-	if allowedPayload != "" {
-		allowedWithPayloadPath := path.Join(testhelper.TestRoot, allowedPayload)
-		bodyWithPayload, err = os.ReadFile(allowedWithPayloadPath)
-		require.NoError(t, err)
-	}
-
+func setup(t *testing.T, userResponses, keyResponses map[string]testResponse) *Client {
+	t.Helper()
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/allowed",
@@ -187,36 +205,14 @@
 				var requestBody *Request
 				require.NoError(t, json.Unmarshal(b, &requestBody))
 
-				switch requestBody.Username {
-				case "first":
-					_, err = w.Write(body)
-					require.NoError(t, err)
-				case "second":
-					errBody := map[string]interface{}{
-						"status":  false,
-						"message": "missing user",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(errBody))
-				case "custom":
-					w.WriteHeader(http.StatusMultipleChoices)
-					_, err = w.Write(bodyWithPayload)
+				if tr, ok := userResponses[requestBody.Username]; ok {
+					w.WriteHeader(tr.status)
+					_, err := w.Write(tr.body)
 					require.NoError(t, err)
-				}
-
-				switch requestBody.KeyId {
-				case "1":
-					_, err = w.Write(body)
+				} else if tr, ok := keyResponses[requestBody.KeyId]; ok {
+					w.WriteHeader(tr.status)
+					_, err := w.Write(tr.body)
 					require.NoError(t, err)
-				case "2":
-					w.WriteHeader(http.StatusForbidden)
-					errBody := &client.ErrorResponse{
-						Message: "Not allowed!",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(errBody))
-				case "3":
-					w.Write([]byte("{ \"message\": \"broken json!\""))
-				case "4":
-					w.WriteHeader(http.StatusForbidden)
 				}
 			},
 		},
# HG changeset patch
# User Jacob Vosmaer <jacob@gitlab.com>
# Date 1642761447 0
#      Fri Jan 21 10:37:27 2022 +0000
# Node ID 27b5f05e21af12f173dd63e0f7f45c5d3b26b549
# Parent  98fea8ab6995c4f9b3d8a13cd5e43847ce23f515
Support parsing `use_sidechannel` API response field

This field will act as a feature flag that controls whether
gitlab-shell uses the old SSHUploadPack RPC or the new
SSHUploadPackWithSidechannel.

diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -32,10 +32,11 @@
 }
 
 type Gitaly struct {
-	Repo     pb.Repository     `json:"repository"`
-	Address  string            `json:"address"`
-	Token    string            `json:"token"`
-	Features map[string]string `json:"features"`
+	Repo           pb.Repository     `json:"repository"`
+	Address        string            `json:"address"`
+	Token          string            `json:"token"`
+	Features       map[string]string `json:"features"`
+	UseSidechannel bool              `json:"use_sidechannel"`
 }
 
 type CustomPayloadData struct {
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -86,6 +86,41 @@
 	}
 }
 
+func TestSidechannelFlag(t *testing.T) {
+	okResponse := testResponse{body: responseBody(t, "allowed_sidechannel.json"), status: http.StatusOK}
+	client := setup(t,
+		map[string]testResponse{"first": okResponse},
+		map[string]testResponse{"1": okResponse},
+	)
+
+	testCases := []struct {
+		desc string
+		args *commandargs.Shell
+		who  string
+	}{
+		{
+			desc: "Provide key id within the request",
+			args: &commandargs.Shell{GitlabKeyId: "1"},
+			who:  "key-1",
+		}, {
+			desc: "Provide username within the request",
+			args: &commandargs.Shell{GitlabUsername: "first"},
+			who:  "user-1",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := client.Verify(context.Background(), tc.args, uploadPackAction, repo)
+			require.NoError(t, err)
+
+			response := buildExpectedResponse(tc.who)
+			response.Gitaly.UseSidechannel = true
+			require.Equal(t, response, result)
+		})
+	}
+}
+
 func TestGeoPushGetCustomAction(t *testing.T) {
 	client := setup(t, map[string]testResponse{
 		"custom": {
diff --git a/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json b/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
@@ -0,0 +1,23 @@
+{
+	"status": true,
+	"gl_repository": "project-26",
+	"gl_project_path": "group/private",
+	"gl_id": "user-1",
+	"gl_username": "root",
+	"git_config_options": ["option"],
+	"gitaly": {
+		"repository": {
+			"storage_name": "default",
+			"relative_path": "@hashed/5f/9c/5f9c4ab08cac7457e9111a30e4664920607ea2c115a1433d7be98e97e64244ca.git",
+			"git_object_directory": "path/to/git_object_directory",
+			"git_alternate_object_directories": ["path/to/git_alternate_object_directory"],
+			"gl_repository": "project-26",
+			"gl_project_path": "group/private"
+		},
+		"address": "unix:gitaly.socket",
+		"token": "token",
+               "use_sidechannel": true
+	},
+	"git_protocol": "protocol",
+	"gl_console_messages": ["console", "message"]
+}
# HG changeset patch
# User Jacob Vosmaer <jacob@gitlab.com>
# Date 1642761101 0
#      Fri Jan 21 10:31:41 2022 +0000
# Node ID 77f2fc579a3820bce3b4e32ccf1b9e5a7dc3868c
# Parent  27b5f05e21af12f173dd63e0f7f45c5d3b26b549
Update gitaly/v14/client to 2e398afa0490ccdf5a82e1a7c7d824ae491eba16

This updates the Gitaly client go.mod dependency to Gitaly commit
2e398afa0490ccdf5a82e1a7c7d824ae491eba16. This causes a grpc-go
version bump, and hence a minor change in some of our test code.

diff --git a/client/testserver/gitalyserver.go b/client/testserver/gitalyserver.go
--- a/client/testserver/gitalyserver.go
+++ b/client/testserver/gitalyserver.go
@@ -13,7 +13,10 @@
 	"google.golang.org/grpc/metadata"
 )
 
-type TestGitalyServer struct{ ReceivedMD metadata.MD }
+type TestGitalyServer struct {
+	ReceivedMD metadata.MD
+	pb.UnimplementedSSHServiceServer
+}
 
 func (s *TestGitalyServer) SSHReceivePack(stream pb.SSHService_SSHReceivePackServer) error {
 	req, err := stream.Recv()
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -11,10 +11,10 @@
 	github.com/pires/go-proxyproto v0.6.0
 	github.com/prometheus/client_golang v1.10.0
 	github.com/stretchr/testify v1.7.0
-	gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1
+	gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490
 	gitlab.com/gitlab-org/labkit v1.7.0
-	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
+	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	google.golang.org/grpc v1.37.0
+	google.golang.org/grpc v1.38.0
 	gopkg.in/yaml.v2 v2.4.0
 )
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,9 @@
+bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
 bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
 cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
@@ -29,20 +31,54 @@
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
 cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/pubsub v1.10.3/go.mod h1:FUcc28GpGxxACoklPsE1sCtbkY4Ix+ro7yvw+h82Jn4=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
-cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.15.0 h1:Ljj+ZXVEhCr/1+4ZhvtteN1ND7UUsNTlduGclLh8GO0=
+cloud.google.com/go/storage v1.15.0/go.mod h1:mjjQMoxxyGH7Jr8K5qrx6N2O0AHsczI61sMNn03GIZI=
+contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=
+contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
+github.com/Azure/azure-amqp-common-go/v3 v3.1.0/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0=
+github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
+github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go v54.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-service-bus-go v0.10.11/go.mod h1:AWw9eTTWZVZyvgpPahD1ybz3a8/vT3GsJDS8KYex55U=
+github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nUdBD+e64lEuc4sVnuOfNs=
+github.com/Azure/go-amqp v0.13.0/go.mod h1:qj+o8xPCz9tMSbQ83Vp8boHahuRDl5mkNHyt1xlxUTs=
+github.com/Azure/go-amqp v0.13.4/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
+github.com/Azure/go-amqp v0.13.7/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
+github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
+github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
+github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
+github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
+github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE=
+github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
+github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
+github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.7/go.mod h1:AkzUsqkrdmNhfP2i54HqINVQopw0CLDnvHpJ88Zz1eI=
+github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
+github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
+github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
+github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
+github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
+github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
+github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
@@ -52,11 +88,15 @@
 github.com/DataDog/datadog-go v4.4.0+incompatible h1:R7WqXWP4fIOAqWJtUKmSfuc7eDsBT58k9AY5WSHVosk=
 github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
 github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
+github.com/GoogleCloudPlatform/cloudsql-proxy v1.22.0/go.mod h1:mAm5O/zik2RFmcpigNjg6nMotDL8ZXJaxKzgGVcSMFA=
 github.com/HdrHistogram/hdrhistogram-go v1.1.0 h1:6dpdDPTRoo78HxAJ6T1HfMiKSnqhgRRqzCuPshRkQ7I=
 github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
 github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
+github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
 github.com/Microsoft/go-winio v0.4.19 h1:ZMZG0O5M8bhD0lgCURV8yu3hQ7TGvQ4L1ZW8+J0j9iE=
 github.com/Microsoft/go-winio v0.4.19/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
 github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
@@ -68,24 +108,30 @@
 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
+github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/alexbrainman/sspi v0.0.0-20180125232955-4729b3d4d858/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
 github.com/avast/retry-go v2.4.2+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
+github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
+github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.37.0 h1:GzFnhOIsrGyQ69s7VgqtrG2BG8v7X7vwB3Xpbd/DBBk=
 github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
+github.com/aws/aws-sdk-go v1.38.35 h1:7AlAO0FC+8nFjxiGKEmq0QLpiA8/XFr6eIxgRTwkdTg=
+github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -115,6 +161,7 @@
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
@@ -123,7 +170,10 @@
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
+github.com/coreos/go-systemd/v22 v22.3.1/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@@ -134,9 +184,16 @@
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
 github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
+github.com/dgryski/go-minhash v0.0.0-20170608043002-7fe510aff544/go.mod h1:VBi0XHpFy0xiMySf6YpVbRqrupW4RprJ5QTyN+XvGSM=
+github.com/dgryski/go-spooky v0.0.0-20170606183049-ed3d087f40e2/go.mod h1:hgHYKsoIw7S/hlWtP7wD1wZ7SX1jPTtKko5X9jrOgPQ=
+github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
+github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/dpotapov/go-spnego v0.0.0-20190506202455-c2c609116ad0/go.mod h1:P4f4MSk7h52F2PK0lCapn5+fu47Uf8aRdxDSqgezxZE=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -146,6 +203,8 @@
 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
 github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
+github.com/ekzhu/minhash-lsh v0.0.0-20171225071031-5c06ee8586a1/go.mod h1:yEtCVi+QamvzjEH4U/m6ZGkALIkF2xfQnFp0BcKmIOk=
+github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
 github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -159,41 +218,62 @@
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
+github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
 github.com/getsentry/raven-go v0.1.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/raven-go v0.1.2/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/sentry-go v0.5.1/go.mod h1:B8H7x8TYDPkeWPRzGpIiFO97LZP6rL8A3hEt8lUItMw=
 github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
-github.com/getsentry/sentry-go v0.10.0 h1:6gwY+66NHKqyZrdi6O2jGdo7wGdo9b3B69E01NFgT5g=
 github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
+github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
 github.com/git-lfs/git-lfs v1.5.1-0.20210304194248-2e1d981afbe3/go.mod h1:8Xqs4mqL7o6xEnaXckIgELARTeK7RYtm3pBab7S79Js=
 github.com/git-lfs/gitobj/v2 v2.0.1/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
 github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
 github.com/git-lfs/wildmatch v1.0.4/go.mod h1:SdHAGnApDpnFYQ0vAxbniWR0sn7yLJ3QXo9RRfhn2ew=
+github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
+github.com/go-enry/go-license-detector/v4 v4.3.0/go.mod h1:HaM4wdNxSlz/9Gw0uVOKSQS5JVFqf2Pk8xUPEn6bldI=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
+github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
+github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
+github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
+github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
+github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
 github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
 github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
 github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
 github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
@@ -204,6 +284,8 @@
 github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -249,6 +331,7 @@
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -265,8 +348,11 @@
 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/go-replayers/grpcreplay v1.0.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
+github.com/google/go-replayers/httpreplay v0.1.2/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -285,10 +371,13 @@
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
+github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
+github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@@ -331,11 +420,14 @@
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 h1:Y4V+SFe7d3iH+9pJCoeWIOS5/xBJIFsltS7E+KJSsJY=
 github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
+github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b/go.mod h1:HmaZGXHdSwQh1jnUlBGN2BeEYOHACLVGzYOXCbsLvxY=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
@@ -345,8 +437,51 @@
 github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
 github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
 github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
+github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
+github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
+github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
+github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
+github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
+github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
+github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
+github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
+github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
+github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
+github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
+github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
+github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
+github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
+github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
+github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
+github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
+github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
+github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
+github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
 github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
 github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jdkato/prose v1.1.0/go.mod h1:jkF0lkxaX5PFSlk9l4Gh9Y+T57TqUZziWT7uZbW5ADg=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
+github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
@@ -363,6 +498,7 @@
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jteeuwen/go-bindata v3.0.8-0.20180305030458-6025e8de665b+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
 github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
@@ -380,8 +516,9 @@
 github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
 github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
 github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
-github.com/kelseyhightower/envconfig v1.3.0 h1:IvRS4f2VcIQy6j4ORGIf9145T/AsUB+oY8LyvN8BXNM=
 github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
+github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -389,21 +526,33 @@
 github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
 github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
 github.com/libgit2/git2go/v31 v31.4.12/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec=
+github.com/libgit2/git2go/v32 v32.1.6/go.mod h1:FAA2ePV5PlLjw1ccncFIvu2v8hJSZVN5IzEn4lo/vwo=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI=
@@ -415,12 +564,17 @@
 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-shellwords v0.0.0-20190425161501-2444a32a19f4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
@@ -445,10 +599,13 @@
 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/montanaflynn/stats v0.0.0-20151014174947-eeaced052adb/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -461,7 +618,7 @@
 github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
 github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
@@ -504,7 +661,6 @@
 github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
 github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
@@ -512,6 +668,7 @@
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
+github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pires/go-proxyproto v0.6.0 h1:cLJUPnuQdiNf7P/wbeOKmM1khVdaMgTFDLj8h9ZrVYk=
 github.com/pires/go-proxyproto v0.6.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -556,6 +713,9 @@
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
+github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
+github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
 github.com/rubenv/sql-migrate v0.0.0-20191213152630-06338513c237/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc=
 github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086/go.mod h1:YpdgDXpumPB/+EGmGTYHeiW/0QVFRzBYTNFaxWfPDk4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -563,17 +723,24 @@
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sebest/xff v0.0.0-20160910043805-6c115e0ffa35/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY=
 github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/shogo82148/go-shuffle v0.0.0-20170808115208-59829097ff3b/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@@ -590,18 +757,21 @@
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/ssgelm/cookiejarparser v1.0.1/go.mod h1:DUfC0mpjIzlDN7DzKjXpHj0qMI5m9VrZuz3wSlI+OEI=
 github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -629,6 +799,8 @@
 github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
+github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
+github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v0.0.0-20170210233622-6b67b3fab74d/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
@@ -643,19 +815,27 @@
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
 gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
 gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
-gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1 h1:4u44IbgntN1yKgnY/mUabRjHXIchrLPwUwMuDyQ0+ec=
 gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
+gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220118113436-b58be10dc69a h1:VeZYyHUa0zvnZwn5hQJMvetcnf+LFUcWJhsqlFsjkEA=
+gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220118113436-b58be10dc69a/go.mod h1:taZCYTkDDPh8LCetkdOEx/x+mkvNgGc9GIGlWkXivk0=
+gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490 h1:NzcVF6tscgsW890MQfVAhMVnAhXeohPaUGOTuFfIJXs=
+gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490/go.mod h1:taZCYTkDDPh8LCetkdOEx/x+mkvNgGc9GIGlWkXivk0=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
+gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
+gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
+gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
 gitlab.com/gitlab-org/labkit v1.7.0 h1:ufG42cvJ+VkqaUWVEoat/h9PGcW9FFSUPQFz7uwL5wc=
 gitlab.com/gitlab-org/labkit v1.7.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
+go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -669,32 +849,49 @@
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
+go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
+gocloud.dev v0.23.0/go.mod h1:zklCCIIo1N9ELkU2S2E7tW8P8eeMU7oGLeQCXdDwx9Q=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -708,6 +905,7 @@
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -733,8 +931,9 @@
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -758,7 +957,9 @@
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -781,8 +982,12 @@
 golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
+golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
+golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
+golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210505214959-0714010a04ed h1:V9kAVxLvz1lkufatrpHuUVyJ/5tR3Ms7rk951P4mI98=
+golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -795,8 +1000,9 @@
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78 h1:rPRtHfUb0UKZeZ6GH4K4Nt4YRbE9V1u+QZX5upZXqJQ=
 golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c h1:SgVl/sCtkicsS7psKkje4H9YtjdEl3xsYh7N+5TDHqY=
+golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -819,8 +1025,10 @@
 golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -832,13 +1040,17 @@
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -855,6 +1067,7 @@
 golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -864,12 +1077,21 @@
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 h1:ZBu6861dZq7xBnG1bn5SRU0vA8nx42at4+kP07FMTog=
+golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
+golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -879,13 +1101,15 @@
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -900,15 +1124,19 @@
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -949,20 +1177,25 @@
 golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
+gonum.org/v1/gonum v0.7.0/go.mod h1:L02bwd0sqlsvRv41G7wGWFCsVNZFv/k1xzGIxeANHGM=
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
@@ -981,13 +1214,15 @@
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
-google.golang.org/api v0.45.0 h1:pqMffJFLBVUDIoYsHcqtxgQVTsmxMDpYLOc5MT4Jrww=
 google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA=
+google.golang.org/api v0.46.0 h1:jkDWHOBIoNSD0OQpq4rtBVu+Rh325MPjXG1rakAp8JU=
+google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
@@ -998,6 +1233,7 @@
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
@@ -1036,8 +1272,12 @@
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
-google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3 h1:K+7Ig5hjiLVA/i1UFUUbCGimWz5/Ey0lAQjT3QiLaPY=
 google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210420162539-3c870d7478d2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2 h1:pl8qT5D+48655f14yDURpIZwSPvMWuuekfAP+gxtjvk=
+google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -1063,8 +1303,9 @@
 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c=
 google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1085,8 +1326,9 @@
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
@@ -1095,6 +1337,7 @@
 gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
 gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
 gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
+gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
 gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
 gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
@@ -1102,6 +1345,7 @@
 gopkg.in/jcmturner/gokrb5.v5 v5.3.0/go.mod h1:oQz8Wc5GsctOTgCVyKad1Vw4TCWz5G6gfIQr88RPv4k=
 gopkg.in/jcmturner/rpc.v0 v0.0.2/go.mod h1:NzMq6cRzR9lipgw7WxRBHNx5N8SifBuaCQsOT1kWY/E=
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -1126,6 +1370,8 @@
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
+nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
# HG changeset patch
# User Jacob Vosmaer <jacob@gitlab.com>
# Date 1642763119 0
#      Fri Jan 21 11:05:19 2022 +0000
# Node ID ea5bfbbec2229fd964ac559b40fbe13021da868f
# Parent  77f2fc579a3820bce3b4e32ccf1b9e5a7dc3868c
Optionally use SSHUploadPackWithSidechannel

If the GitLab API returns an allowed response with use_sidechannel set
to true, gitlab-shell will establish a sidechannel connection and use
SSHUploadPackWithSidechannel instead of SSHUploadPack. This is an
efficiency improvement.

diff --git a/client/testserver/gitalyserver.go b/client/testserver/gitalyserver.go
--- a/client/testserver/gitalyserver.go
+++ b/client/testserver/gitalyserver.go
@@ -1,6 +1,8 @@
 package testserver
 
 import (
+	"context"
+	"fmt"
 	"net"
 	"os"
 	"path"
@@ -8,8 +10,11 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/labkit/log"
 	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/metadata"
 )
 
@@ -46,6 +51,26 @@
 	return nil
 }
 
+func (s *TestGitalyServer) SSHUploadPackWithSidechannel(ctx context.Context, req *pb.SSHUploadPackWithSidechannelRequest) (*pb.SSHUploadPackWithSidechannelResponse, error) {
+	conn, err := client.OpenServerSidechannel(ctx)
+	if err != nil {
+		return nil, err
+	}
+	defer conn.Close()
+
+	s.ReceivedMD, _ = metadata.FromIncomingContext(ctx)
+
+	response := []byte("SSHUploadPackWithSidechannel: " + req.Repository.GlRepository)
+	if _, err := fmt.Fprintf(conn, "%04x\x01%s", len(response)+5, response); err != nil {
+		return nil, err
+	}
+	if err := conn.Close(); err != nil {
+		return nil, err
+	}
+
+	return &pb.SSHUploadPackWithSidechannelResponse{}, nil
+}
+
 func (s *TestGitalyServer) SSHUploadArchive(stream pb.SSHService_SSHUploadArchiveServer) error {
 	req, err := stream.Recv()
 	if err != nil {
@@ -70,7 +95,9 @@
 	err := os.MkdirAll(filepath.Dir(gitalySocketPath), 0700)
 	require.NoError(t, err)
 
-	server := grpc.NewServer()
+	server := grpc.NewServer(
+		client.SidechannelServer(log.ContextLogger(context.Background()), insecure.NewCredentials()),
+	)
 
 	listener, err := net.Listen("unix", gitalySocketPath)
 	require.NoError(t, err)
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -30,7 +30,7 @@
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
 		defer cancel()
 
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -23,7 +23,7 @@
 
 	request := &pb.SSHUploadArchiveRequest{Repository: &response.Gitaly.Repo}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
 		defer cancel()
 
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -21,13 +21,30 @@
 		Features:    response.Gitaly.Features,
 	}
 
+	if response.Gitaly.UseSidechannel {
+		gc.DialSidechannel = true
+		request := &pb.SSHUploadPackWithSidechannelRequest{
+			Repository:       &response.Gitaly.Repo,
+			GitProtocol:      c.Args.Env.GitProtocolVersion,
+			GitConfigOptions: response.GitConfigOptions,
+		}
+
+		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
+			ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+			defer cancel()
+
+			rw := c.ReadWriter
+			return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
+		})
+	}
+
 	request := &pb.SSHUploadPackRequest{
 		Repository:       &response.Gitaly.Repo,
 		GitProtocol:      c.Args.Env.GitProtocolVersion,
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
 		defer cancel()
 
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -71,3 +71,59 @@
 	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
 	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 }
+
+func TestUploadPack_withSidechannel(t *testing.T) {
+	gitalyAddress, testServer := testserver.StartGitalyServer(t)
+
+	requests := requesthandlers.BuildAllowedWithGitalyHandlersWithSidechannel(t, gitalyAddress)
+	url := testserver.StartHttpServer(t, requests)
+
+	output := &bytes.Buffer{}
+	input := &bytes.Buffer{}
+
+	userId := "1"
+	repo := "group/repo"
+
+	env := sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: "git-upload-pack " + repo,
+		RemoteAddr:      "127.0.0.1",
+	}
+
+	args := &commandargs.Shell{
+		GitlabKeyId: userId,
+		CommandType: commandargs.UploadPack,
+		SshArgs:     []string{"git-upload-pack", repo},
+		Env:         env,
+	}
+
+	cmd := &Command{
+		Config:     &config.Config{GitlabUrl: url},
+		Args:       args,
+		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
+	}
+
+	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
+	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
+
+	err := cmd.Execute(ctx)
+	require.NoError(t, err)
+
+	require.Equal(t, "SSHUploadPackWithSidechannel: "+repo, output.String())
+
+	for k, v := range map[string]string{
+		"gitaly-feature-cache_invalidator":        "true",
+		"gitaly-feature-inforef_uploadpack_cache": "false",
+		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-pack",
+		"key_id":                                  "123",
+		"user_id":                                 "1",
+		"remote_ip":                               "127.0.0.1",
+		"key_type":                                "key",
+	} {
+		actual := testServer.ReceivedMD[k]
+		require.Len(t, actual, 1)
+		require.Equal(t, v, actual[0])
+	}
+	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
+}
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -29,21 +29,23 @@
 // GitalyHandlerFunc implementations are responsible for making
 // an appropriate Gitaly call using the provided client and context
 // and returning an error from the Gitaly call.
-type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn) (int32, error)
+type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error)
 
 type GitalyCommand struct {
-	Config      *config.Config
-	ServiceName string
-	Address     string
-	Token       string
-	Features    map[string]string
+	Config          *config.Config
+	ServiceName     string
+	Address         string
+	Token           string
+	Features        map[string]string
+	DialSidechannel bool
 }
 
 // RunGitalyCommand provides a bootstrap for Gitaly commands executed
 // through GitLab-Shell. It ensures that logging, tracing and other
 // common concerns are configured before executing the `handler`.
 func (gc *GitalyCommand) RunGitalyCommand(ctx context.Context, handler GitalyHandlerFunc) error {
-	conn, err := getConn(ctx, gc)
+	registry := client.NewSidechannelRegistry(log.ContextLogger(ctx))
+	conn, err := getConn(ctx, gc, registry)
 	if err != nil {
 		log.ContextLogger(ctx).WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Failed to get connection to execute Git command")
 
@@ -53,7 +55,7 @@
 
 	childCtx := withOutgoingMetadata(ctx, gc.Features)
 	ctxlog := log.ContextLogger(childCtx)
-	exitStatus, err := handler(childCtx, conn)
+	exitStatus, err := handler(childCtx, conn, registry)
 
 	if err != nil {
 		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
@@ -116,7 +118,7 @@
 	return metadata.NewOutgoingContext(ctx, md)
 }
 
-func getConn(ctx context.Context, gc *GitalyCommand) (*grpc.ClientConn, error) {
+func getConn(ctx context.Context, gc *GitalyCommand, registry *client.SidechannelRegistry) (*grpc.ClientConn, error) {
 	if gc.Address == "" {
 		return nil, fmt.Errorf("no gitaly_address given")
 	}
@@ -160,5 +162,9 @@
 		)
 	}
 
+	if gc.DialSidechannel {
+		return client.DialSidechannel(ctx, gc.Address, registry, connOpts)
+	}
+
 	return client.DialContext(ctx, gc.Address, connOpts)
 }
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -11,14 +11,15 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
+	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
-func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn) (int32, error) {
-	return func(ctx context.Context, client *grpc.ClientConn) (int32, error) {
+func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn, *client.SidechannelRegistry) (int32, error) {
+	return func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		require.NotNil(t, ctx)
 		require.NotNil(t, client)
 
@@ -86,7 +87,7 @@
 		t.Run(tt.name, func(t *testing.T) {
 			cmd := tt.gc
 
-			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn) (int32, error) {
+			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn, _ *client.SidechannelRegistry) (int32, error) {
 				md, exists := metadata.FromOutgoingContext(ctx)
 				require.True(t, exists)
 				require.Equal(t, len(tt.want), md.Len())
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -29,6 +29,14 @@
 }
 
 func BuildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
+	return buildAllowedWithGitalyHandlers(t, gitalyAddress, false)
+}
+
+func BuildAllowedWithGitalyHandlersWithSidechannel(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
+	return buildAllowedWithGitalyHandlers(t, gitalyAddress, true)
+}
+
+func buildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string, useSidechannel bool) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/allowed",
@@ -56,6 +64,9 @@
 						},
 					},
 				}
+				if useSidechannel {
+					body["gitaly"].(map[string]interface{})["use_sidechannel"] = true
+				}
 				require.NoError(t, json.NewEncoder(w).Encode(body))
 			},
 		},
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1643110793 0
#      Tue Jan 25 11:39:53 2022 +0000
# Node ID 3b5a0ff21f311a0075abc54e810ea0f82945d627
# Parent  72999fcfa91c17621e5aa8d7c5b1a0d5397c9534
# Parent  ea5bfbbec2229fd964ac559b40fbe13021da868f
Merge branch 'jv-ssh-sidechannel' into 'main'

Add support for SSHUploadPackWithSidechannel RPC

See merge request gitlab-org/gitlab-shell!557

diff --git a/client/testserver/gitalyserver.go b/client/testserver/gitalyserver.go
--- a/client/testserver/gitalyserver.go
+++ b/client/testserver/gitalyserver.go
@@ -1,6 +1,8 @@
 package testserver
 
 import (
+	"context"
+	"fmt"
 	"net"
 	"os"
 	"path"
@@ -8,12 +10,18 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/labkit/log"
 	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/metadata"
 )
 
-type TestGitalyServer struct{ ReceivedMD metadata.MD }
+type TestGitalyServer struct {
+	ReceivedMD metadata.MD
+	pb.UnimplementedSSHServiceServer
+}
 
 func (s *TestGitalyServer) SSHReceivePack(stream pb.SSHService_SSHReceivePackServer) error {
 	req, err := stream.Recv()
@@ -43,6 +51,26 @@
 	return nil
 }
 
+func (s *TestGitalyServer) SSHUploadPackWithSidechannel(ctx context.Context, req *pb.SSHUploadPackWithSidechannelRequest) (*pb.SSHUploadPackWithSidechannelResponse, error) {
+	conn, err := client.OpenServerSidechannel(ctx)
+	if err != nil {
+		return nil, err
+	}
+	defer conn.Close()
+
+	s.ReceivedMD, _ = metadata.FromIncomingContext(ctx)
+
+	response := []byte("SSHUploadPackWithSidechannel: " + req.Repository.GlRepository)
+	if _, err := fmt.Fprintf(conn, "%04x\x01%s", len(response)+5, response); err != nil {
+		return nil, err
+	}
+	if err := conn.Close(); err != nil {
+		return nil, err
+	}
+
+	return &pb.SSHUploadPackWithSidechannelResponse{}, nil
+}
+
 func (s *TestGitalyServer) SSHUploadArchive(stream pb.SSHService_SSHUploadArchiveServer) error {
 	req, err := stream.Recv()
 	if err != nil {
@@ -67,7 +95,9 @@
 	err := os.MkdirAll(filepath.Dir(gitalySocketPath), 0700)
 	require.NoError(t, err)
 
-	server := grpc.NewServer()
+	server := grpc.NewServer(
+		client.SidechannelServer(log.ContextLogger(context.Background()), insecure.NewCredentials()),
+	)
 
 	listener, err := net.Listen("unix", gitalySocketPath)
 	require.NoError(t, err)
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -11,10 +11,10 @@
 	github.com/pires/go-proxyproto v0.6.0
 	github.com/prometheus/client_golang v1.10.0
 	github.com/stretchr/testify v1.7.0
-	gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1
+	gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490
 	gitlab.com/gitlab-org/labkit v1.7.0
-	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
+	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	google.golang.org/grpc v1.37.0
+	google.golang.org/grpc v1.38.0
 	gopkg.in/yaml.v2 v2.4.0
 )
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,9 @@
+bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
 bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
 cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
@@ -29,20 +31,54 @@
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
 cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/pubsub v1.10.3/go.mod h1:FUcc28GpGxxACoklPsE1sCtbkY4Ix+ro7yvw+h82Jn4=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
-cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.15.0 h1:Ljj+ZXVEhCr/1+4ZhvtteN1ND7UUsNTlduGclLh8GO0=
+cloud.google.com/go/storage v1.15.0/go.mod h1:mjjQMoxxyGH7Jr8K5qrx6N2O0AHsczI61sMNn03GIZI=
+contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=
+contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
+github.com/Azure/azure-amqp-common-go/v3 v3.1.0/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0=
+github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
+github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go v54.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-service-bus-go v0.10.11/go.mod h1:AWw9eTTWZVZyvgpPahD1ybz3a8/vT3GsJDS8KYex55U=
+github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nUdBD+e64lEuc4sVnuOfNs=
+github.com/Azure/go-amqp v0.13.0/go.mod h1:qj+o8xPCz9tMSbQ83Vp8boHahuRDl5mkNHyt1xlxUTs=
+github.com/Azure/go-amqp v0.13.4/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
+github.com/Azure/go-amqp v0.13.7/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
+github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
+github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
+github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
+github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
+github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE=
+github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
+github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
+github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.7/go.mod h1:AkzUsqkrdmNhfP2i54HqINVQopw0CLDnvHpJ88Zz1eI=
+github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
+github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
+github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
+github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
+github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
+github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
+github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
@@ -52,11 +88,15 @@
 github.com/DataDog/datadog-go v4.4.0+incompatible h1:R7WqXWP4fIOAqWJtUKmSfuc7eDsBT58k9AY5WSHVosk=
 github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
 github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
+github.com/GoogleCloudPlatform/cloudsql-proxy v1.22.0/go.mod h1:mAm5O/zik2RFmcpigNjg6nMotDL8ZXJaxKzgGVcSMFA=
 github.com/HdrHistogram/hdrhistogram-go v1.1.0 h1:6dpdDPTRoo78HxAJ6T1HfMiKSnqhgRRqzCuPshRkQ7I=
 github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
 github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
+github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
 github.com/Microsoft/go-winio v0.4.19 h1:ZMZG0O5M8bhD0lgCURV8yu3hQ7TGvQ4L1ZW8+J0j9iE=
 github.com/Microsoft/go-winio v0.4.19/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
 github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
@@ -68,24 +108,30 @@
 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
+github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/alexbrainman/sspi v0.0.0-20180125232955-4729b3d4d858/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
 github.com/avast/retry-go v2.4.2+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
+github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
+github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.37.0 h1:GzFnhOIsrGyQ69s7VgqtrG2BG8v7X7vwB3Xpbd/DBBk=
 github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
+github.com/aws/aws-sdk-go v1.38.35 h1:7AlAO0FC+8nFjxiGKEmq0QLpiA8/XFr6eIxgRTwkdTg=
+github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -115,6 +161,7 @@
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
@@ -123,7 +170,10 @@
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
+github.com/coreos/go-systemd/v22 v22.3.1/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@@ -134,9 +184,16 @@
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
 github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
+github.com/dgryski/go-minhash v0.0.0-20170608043002-7fe510aff544/go.mod h1:VBi0XHpFy0xiMySf6YpVbRqrupW4RprJ5QTyN+XvGSM=
+github.com/dgryski/go-spooky v0.0.0-20170606183049-ed3d087f40e2/go.mod h1:hgHYKsoIw7S/hlWtP7wD1wZ7SX1jPTtKko5X9jrOgPQ=
+github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
+github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/dpotapov/go-spnego v0.0.0-20190506202455-c2c609116ad0/go.mod h1:P4f4MSk7h52F2PK0lCapn5+fu47Uf8aRdxDSqgezxZE=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -146,6 +203,8 @@
 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
 github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
+github.com/ekzhu/minhash-lsh v0.0.0-20171225071031-5c06ee8586a1/go.mod h1:yEtCVi+QamvzjEH4U/m6ZGkALIkF2xfQnFp0BcKmIOk=
+github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
 github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -159,41 +218,62 @@
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
+github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
 github.com/getsentry/raven-go v0.1.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/raven-go v0.1.2/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/sentry-go v0.5.1/go.mod h1:B8H7x8TYDPkeWPRzGpIiFO97LZP6rL8A3hEt8lUItMw=
 github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
-github.com/getsentry/sentry-go v0.10.0 h1:6gwY+66NHKqyZrdi6O2jGdo7wGdo9b3B69E01NFgT5g=
 github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
+github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
 github.com/git-lfs/git-lfs v1.5.1-0.20210304194248-2e1d981afbe3/go.mod h1:8Xqs4mqL7o6xEnaXckIgELARTeK7RYtm3pBab7S79Js=
 github.com/git-lfs/gitobj/v2 v2.0.1/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
 github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
 github.com/git-lfs/wildmatch v1.0.4/go.mod h1:SdHAGnApDpnFYQ0vAxbniWR0sn7yLJ3QXo9RRfhn2ew=
+github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
+github.com/go-enry/go-license-detector/v4 v4.3.0/go.mod h1:HaM4wdNxSlz/9Gw0uVOKSQS5JVFqf2Pk8xUPEn6bldI=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
+github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
+github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
+github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
+github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
+github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
 github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
 github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
 github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
 github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
@@ -204,6 +284,8 @@
 github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -249,6 +331,7 @@
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -265,8 +348,11 @@
 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/go-replayers/grpcreplay v1.0.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
+github.com/google/go-replayers/httpreplay v0.1.2/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -285,10 +371,13 @@
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
+github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
+github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@@ -331,11 +420,14 @@
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 h1:Y4V+SFe7d3iH+9pJCoeWIOS5/xBJIFsltS7E+KJSsJY=
 github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
+github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b/go.mod h1:HmaZGXHdSwQh1jnUlBGN2BeEYOHACLVGzYOXCbsLvxY=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
@@ -345,8 +437,51 @@
 github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
 github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
 github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
+github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
+github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
+github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
+github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
+github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
+github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
+github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
+github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
+github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
+github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
+github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
+github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
+github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
+github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
+github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
+github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
+github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
+github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
+github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
+github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
 github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
 github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jdkato/prose v1.1.0/go.mod h1:jkF0lkxaX5PFSlk9l4Gh9Y+T57TqUZziWT7uZbW5ADg=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
+github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
@@ -363,6 +498,7 @@
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jteeuwen/go-bindata v3.0.8-0.20180305030458-6025e8de665b+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
 github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
@@ -380,8 +516,9 @@
 github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
 github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
 github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
-github.com/kelseyhightower/envconfig v1.3.0 h1:IvRS4f2VcIQy6j4ORGIf9145T/AsUB+oY8LyvN8BXNM=
 github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
+github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -389,21 +526,33 @@
 github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
 github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
 github.com/libgit2/git2go/v31 v31.4.12/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec=
+github.com/libgit2/git2go/v32 v32.1.6/go.mod h1:FAA2ePV5PlLjw1ccncFIvu2v8hJSZVN5IzEn4lo/vwo=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI=
@@ -415,12 +564,17 @@
 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-shellwords v0.0.0-20190425161501-2444a32a19f4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
@@ -445,10 +599,13 @@
 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/montanaflynn/stats v0.0.0-20151014174947-eeaced052adb/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -461,7 +618,7 @@
 github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
 github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
@@ -504,7 +661,6 @@
 github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
 github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
@@ -512,6 +668,7 @@
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
+github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pires/go-proxyproto v0.6.0 h1:cLJUPnuQdiNf7P/wbeOKmM1khVdaMgTFDLj8h9ZrVYk=
 github.com/pires/go-proxyproto v0.6.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -556,6 +713,9 @@
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
+github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
+github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
 github.com/rubenv/sql-migrate v0.0.0-20191213152630-06338513c237/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc=
 github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086/go.mod h1:YpdgDXpumPB/+EGmGTYHeiW/0QVFRzBYTNFaxWfPDk4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -563,17 +723,24 @@
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sebest/xff v0.0.0-20160910043805-6c115e0ffa35/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY=
 github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/shogo82148/go-shuffle v0.0.0-20170808115208-59829097ff3b/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@@ -590,18 +757,21 @@
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/ssgelm/cookiejarparser v1.0.1/go.mod h1:DUfC0mpjIzlDN7DzKjXpHj0qMI5m9VrZuz3wSlI+OEI=
 github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -629,6 +799,8 @@
 github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
+github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
+github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v0.0.0-20170210233622-6b67b3fab74d/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
@@ -643,19 +815,27 @@
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
 gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
 gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
-gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1 h1:4u44IbgntN1yKgnY/mUabRjHXIchrLPwUwMuDyQ0+ec=
 gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
+gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220118113436-b58be10dc69a h1:VeZYyHUa0zvnZwn5hQJMvetcnf+LFUcWJhsqlFsjkEA=
+gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220118113436-b58be10dc69a/go.mod h1:taZCYTkDDPh8LCetkdOEx/x+mkvNgGc9GIGlWkXivk0=
+gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490 h1:NzcVF6tscgsW890MQfVAhMVnAhXeohPaUGOTuFfIJXs=
+gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490/go.mod h1:taZCYTkDDPh8LCetkdOEx/x+mkvNgGc9GIGlWkXivk0=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
+gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
+gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
+gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
 gitlab.com/gitlab-org/labkit v1.7.0 h1:ufG42cvJ+VkqaUWVEoat/h9PGcW9FFSUPQFz7uwL5wc=
 gitlab.com/gitlab-org/labkit v1.7.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
+go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -669,32 +849,49 @@
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
+go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
+gocloud.dev v0.23.0/go.mod h1:zklCCIIo1N9ELkU2S2E7tW8P8eeMU7oGLeQCXdDwx9Q=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -708,6 +905,7 @@
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -733,8 +931,9 @@
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -758,7 +957,9 @@
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -781,8 +982,12 @@
 golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
+golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
+golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
+golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210505214959-0714010a04ed h1:V9kAVxLvz1lkufatrpHuUVyJ/5tR3Ms7rk951P4mI98=
+golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -795,8 +1000,9 @@
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78 h1:rPRtHfUb0UKZeZ6GH4K4Nt4YRbE9V1u+QZX5upZXqJQ=
 golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c h1:SgVl/sCtkicsS7psKkje4H9YtjdEl3xsYh7N+5TDHqY=
+golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -819,8 +1025,10 @@
 golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -832,13 +1040,17 @@
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -855,6 +1067,7 @@
 golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -864,12 +1077,21 @@
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 h1:ZBu6861dZq7xBnG1bn5SRU0vA8nx42at4+kP07FMTog=
+golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
+golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -879,13 +1101,15 @@
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -900,15 +1124,19 @@
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -949,20 +1177,25 @@
 golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
+gonum.org/v1/gonum v0.7.0/go.mod h1:L02bwd0sqlsvRv41G7wGWFCsVNZFv/k1xzGIxeANHGM=
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
@@ -981,13 +1214,15 @@
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
-google.golang.org/api v0.45.0 h1:pqMffJFLBVUDIoYsHcqtxgQVTsmxMDpYLOc5MT4Jrww=
 google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA=
+google.golang.org/api v0.46.0 h1:jkDWHOBIoNSD0OQpq4rtBVu+Rh325MPjXG1rakAp8JU=
+google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
@@ -998,6 +1233,7 @@
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
@@ -1036,8 +1272,12 @@
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
-google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3 h1:K+7Ig5hjiLVA/i1UFUUbCGimWz5/Ey0lAQjT3QiLaPY=
 google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210420162539-3c870d7478d2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2 h1:pl8qT5D+48655f14yDURpIZwSPvMWuuekfAP+gxtjvk=
+google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -1063,8 +1303,9 @@
 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c=
 google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1085,8 +1326,9 @@
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
@@ -1095,6 +1337,7 @@
 gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
 gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
 gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
+gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
 gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
 gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
@@ -1102,6 +1345,7 @@
 gopkg.in/jcmturner/gokrb5.v5 v5.3.0/go.mod h1:oQz8Wc5GsctOTgCVyKad1Vw4TCWz5G6gfIQr88RPv4k=
 gopkg.in/jcmturner/rpc.v0 v0.0.2/go.mod h1:NzMq6cRzR9lipgw7WxRBHNx5N8SifBuaCQsOT1kWY/E=
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -1126,6 +1370,8 @@
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
+nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -30,7 +30,7 @@
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
 		defer cancel()
 
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -23,7 +23,7 @@
 
 	request := &pb.SSHUploadArchiveRequest{Repository: &response.Gitaly.Repo}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
 		defer cancel()
 
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -21,13 +21,30 @@
 		Features:    response.Gitaly.Features,
 	}
 
+	if response.Gitaly.UseSidechannel {
+		gc.DialSidechannel = true
+		request := &pb.SSHUploadPackWithSidechannelRequest{
+			Repository:       &response.Gitaly.Repo,
+			GitProtocol:      c.Args.Env.GitProtocolVersion,
+			GitConfigOptions: response.GitConfigOptions,
+		}
+
+		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
+			ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+			defer cancel()
+
+			rw := c.ReadWriter
+			return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
+		})
+	}
+
 	request := &pb.SSHUploadPackRequest{
 		Repository:       &response.Gitaly.Repo,
 		GitProtocol:      c.Args.Env.GitProtocolVersion,
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
 		defer cancel()
 
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -71,3 +71,59 @@
 	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
 	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 }
+
+func TestUploadPack_withSidechannel(t *testing.T) {
+	gitalyAddress, testServer := testserver.StartGitalyServer(t)
+
+	requests := requesthandlers.BuildAllowedWithGitalyHandlersWithSidechannel(t, gitalyAddress)
+	url := testserver.StartHttpServer(t, requests)
+
+	output := &bytes.Buffer{}
+	input := &bytes.Buffer{}
+
+	userId := "1"
+	repo := "group/repo"
+
+	env := sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: "git-upload-pack " + repo,
+		RemoteAddr:      "127.0.0.1",
+	}
+
+	args := &commandargs.Shell{
+		GitlabKeyId: userId,
+		CommandType: commandargs.UploadPack,
+		SshArgs:     []string{"git-upload-pack", repo},
+		Env:         env,
+	}
+
+	cmd := &Command{
+		Config:     &config.Config{GitlabUrl: url},
+		Args:       args,
+		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
+	}
+
+	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
+	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
+
+	err := cmd.Execute(ctx)
+	require.NoError(t, err)
+
+	require.Equal(t, "SSHUploadPackWithSidechannel: "+repo, output.String())
+
+	for k, v := range map[string]string{
+		"gitaly-feature-cache_invalidator":        "true",
+		"gitaly-feature-inforef_uploadpack_cache": "false",
+		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-pack",
+		"key_id":                                  "123",
+		"user_id":                                 "1",
+		"remote_ip":                               "127.0.0.1",
+		"key_type":                                "key",
+	} {
+		actual := testServer.ReceivedMD[k]
+		require.Len(t, actual, 1)
+		require.Equal(t, v, actual[0])
+	}
+	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
+}
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -32,10 +32,11 @@
 }
 
 type Gitaly struct {
-	Repo     pb.Repository     `json:"repository"`
-	Address  string            `json:"address"`
-	Token    string            `json:"token"`
-	Features map[string]string `json:"features"`
+	Repo           pb.Repository     `json:"repository"`
+	Address        string            `json:"address"`
+	Token          string            `json:"token"`
+	Features       map[string]string `json:"features"`
+	UseSidechannel bool              `json:"use_sidechannel"`
 }
 
 type CustomPayloadData struct {
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -11,7 +11,6 @@
 
 	"github.com/stretchr/testify/require"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -54,7 +53,11 @@
 }
 
 func TestSuccessfulResponses(t *testing.T) {
-	client := setup(t, "")
+	okResponse := testResponse{body: responseBody(t, "allowed.json"), status: http.StatusOK}
+	client := setup(t,
+		map[string]testResponse{"first": okResponse},
+		map[string]testResponse{"1": okResponse},
+	)
 
 	testCases := []struct {
 		desc string
@@ -83,8 +86,48 @@
 	}
 }
 
+func TestSidechannelFlag(t *testing.T) {
+	okResponse := testResponse{body: responseBody(t, "allowed_sidechannel.json"), status: http.StatusOK}
+	client := setup(t,
+		map[string]testResponse{"first": okResponse},
+		map[string]testResponse{"1": okResponse},
+	)
+
+	testCases := []struct {
+		desc string
+		args *commandargs.Shell
+		who  string
+	}{
+		{
+			desc: "Provide key id within the request",
+			args: &commandargs.Shell{GitlabKeyId: "1"},
+			who:  "key-1",
+		}, {
+			desc: "Provide username within the request",
+			args: &commandargs.Shell{GitlabUsername: "first"},
+			who:  "user-1",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := client.Verify(context.Background(), tc.args, uploadPackAction, repo)
+			require.NoError(t, err)
+
+			response := buildExpectedResponse(tc.who)
+			response.Gitaly.UseSidechannel = true
+			require.Equal(t, response, result)
+		})
+	}
+}
+
 func TestGeoPushGetCustomAction(t *testing.T) {
-	client := setup(t, "responses/allowed_with_push_payload.json")
+	client := setup(t, map[string]testResponse{
+		"custom": {
+			body:   responseBody(t, "allowed_with_push_payload.json"),
+			status: 300,
+		},
+	}, nil)
 
 	args := &commandargs.Shell{GitlabUsername: "custom"}
 	result, err := client.Verify(context.Background(), args, receivePackAction, repo)
@@ -106,7 +149,12 @@
 }
 
 func TestGeoPullGetCustomAction(t *testing.T) {
-	client := setup(t, "responses/allowed_with_pull_payload.json")
+	client := setup(t, map[string]testResponse{
+		"custom": {
+			body:   responseBody(t, "allowed_with_pull_payload.json"),
+			status: 300,
+		},
+	}, nil)
 
 	args := &commandargs.Shell{GitlabUsername: "custom"}
 	result, err := client.Verify(context.Background(), args, uploadPackAction, repo)
@@ -128,7 +176,11 @@
 }
 
 func TestErrorResponses(t *testing.T) {
-	client := setup(t, "")
+	client := setup(t, nil, map[string]testResponse{
+		"2": {body: []byte(`{"message":"Not allowed!"}`), status: http.StatusForbidden},
+		"3": {body: []byte(`{"message":"broken json!`), status: http.StatusOK},
+		"4": {status: http.StatusForbidden},
+	})
 
 	testCases := []struct {
 		desc          string
@@ -163,20 +215,21 @@
 	}
 }
 
-func setup(t *testing.T, allowedPayload string) *Client {
-	testhelper.PrepareTestRootDir(t)
-
-	body, err := os.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
-	require.NoError(t, err)
+type testResponse struct {
+	body   []byte
+	status int
+}
 
-	var bodyWithPayload []byte
+func responseBody(t *testing.T, name string) []byte {
+	t.Helper()
+	testhelper.PrepareTestRootDir(t)
+	body, err := os.ReadFile(path.Join(testhelper.TestRoot, "responses", name))
+	require.NoError(t, err)
+	return body
+}
 
-	if allowedPayload != "" {
-		allowedWithPayloadPath := path.Join(testhelper.TestRoot, allowedPayload)
-		bodyWithPayload, err = os.ReadFile(allowedWithPayloadPath)
-		require.NoError(t, err)
-	}
-
+func setup(t *testing.T, userResponses, keyResponses map[string]testResponse) *Client {
+	t.Helper()
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/allowed",
@@ -187,36 +240,14 @@
 				var requestBody *Request
 				require.NoError(t, json.Unmarshal(b, &requestBody))
 
-				switch requestBody.Username {
-				case "first":
-					_, err = w.Write(body)
-					require.NoError(t, err)
-				case "second":
-					errBody := map[string]interface{}{
-						"status":  false,
-						"message": "missing user",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(errBody))
-				case "custom":
-					w.WriteHeader(http.StatusMultipleChoices)
-					_, err = w.Write(bodyWithPayload)
+				if tr, ok := userResponses[requestBody.Username]; ok {
+					w.WriteHeader(tr.status)
+					_, err := w.Write(tr.body)
 					require.NoError(t, err)
-				}
-
-				switch requestBody.KeyId {
-				case "1":
-					_, err = w.Write(body)
+				} else if tr, ok := keyResponses[requestBody.KeyId]; ok {
+					w.WriteHeader(tr.status)
+					_, err := w.Write(tr.body)
 					require.NoError(t, err)
-				case "2":
-					w.WriteHeader(http.StatusForbidden)
-					errBody := &client.ErrorResponse{
-						Message: "Not allowed!",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(errBody))
-				case "3":
-					w.Write([]byte("{ \"message\": \"broken json!\""))
-				case "4":
-					w.WriteHeader(http.StatusForbidden)
 				}
 			},
 		},
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -29,21 +29,23 @@
 // GitalyHandlerFunc implementations are responsible for making
 // an appropriate Gitaly call using the provided client and context
 // and returning an error from the Gitaly call.
-type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn) (int32, error)
+type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error)
 
 type GitalyCommand struct {
-	Config      *config.Config
-	ServiceName string
-	Address     string
-	Token       string
-	Features    map[string]string
+	Config          *config.Config
+	ServiceName     string
+	Address         string
+	Token           string
+	Features        map[string]string
+	DialSidechannel bool
 }
 
 // RunGitalyCommand provides a bootstrap for Gitaly commands executed
 // through GitLab-Shell. It ensures that logging, tracing and other
 // common concerns are configured before executing the `handler`.
 func (gc *GitalyCommand) RunGitalyCommand(ctx context.Context, handler GitalyHandlerFunc) error {
-	conn, err := getConn(ctx, gc)
+	registry := client.NewSidechannelRegistry(log.ContextLogger(ctx))
+	conn, err := getConn(ctx, gc, registry)
 	if err != nil {
 		log.ContextLogger(ctx).WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Failed to get connection to execute Git command")
 
@@ -53,7 +55,7 @@
 
 	childCtx := withOutgoingMetadata(ctx, gc.Features)
 	ctxlog := log.ContextLogger(childCtx)
-	exitStatus, err := handler(childCtx, conn)
+	exitStatus, err := handler(childCtx, conn, registry)
 
 	if err != nil {
 		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
@@ -116,7 +118,7 @@
 	return metadata.NewOutgoingContext(ctx, md)
 }
 
-func getConn(ctx context.Context, gc *GitalyCommand) (*grpc.ClientConn, error) {
+func getConn(ctx context.Context, gc *GitalyCommand, registry *client.SidechannelRegistry) (*grpc.ClientConn, error) {
 	if gc.Address == "" {
 		return nil, fmt.Errorf("no gitaly_address given")
 	}
@@ -160,5 +162,9 @@
 		)
 	}
 
+	if gc.DialSidechannel {
+		return client.DialSidechannel(ctx, gc.Address, registry, connOpts)
+	}
+
 	return client.DialContext(ctx, gc.Address, connOpts)
 }
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -11,14 +11,15 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
+	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
-func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn) (int32, error) {
-	return func(ctx context.Context, client *grpc.ClientConn) (int32, error) {
+func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn, *client.SidechannelRegistry) (int32, error) {
+	return func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		require.NotNil(t, ctx)
 		require.NotNil(t, client)
 
@@ -86,7 +87,7 @@
 		t.Run(tt.name, func(t *testing.T) {
 			cmd := tt.gc
 
-			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn) (int32, error) {
+			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn, _ *client.SidechannelRegistry) (int32, error) {
 				md, exists := metadata.FromOutgoingContext(ctx)
 				require.True(t, exists)
 				require.Equal(t, len(tt.want), md.Len())
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -29,6 +29,14 @@
 }
 
 func BuildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
+	return buildAllowedWithGitalyHandlers(t, gitalyAddress, false)
+}
+
+func BuildAllowedWithGitalyHandlersWithSidechannel(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
+	return buildAllowedWithGitalyHandlers(t, gitalyAddress, true)
+}
+
+func buildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string, useSidechannel bool) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/allowed",
@@ -56,6 +64,9 @@
 						},
 					},
 				}
+				if useSidechannel {
+					body["gitaly"].(map[string]interface{})["use_sidechannel"] = true
+				}
 				require.NoError(t, json.NewEncoder(w).Encode(body))
 			},
 		},
diff --git a/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json b/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
@@ -0,0 +1,23 @@
+{
+	"status": true,
+	"gl_repository": "project-26",
+	"gl_project_path": "group/private",
+	"gl_id": "user-1",
+	"gl_username": "root",
+	"git_config_options": ["option"],
+	"gitaly": {
+		"repository": {
+			"storage_name": "default",
+			"relative_path": "@hashed/5f/9c/5f9c4ab08cac7457e9111a30e4664920607ea2c115a1433d7be98e97e64244ca.git",
+			"git_object_directory": "path/to/git_object_directory",
+			"git_alternate_object_directories": ["path/to/git_alternate_object_directory"],
+			"gl_repository": "project-26",
+			"gl_project_path": "group/private"
+		},
+		"address": "unix:gitaly.socket",
+		"token": "token",
+               "use_sidechannel": true
+	},
+	"git_protocol": "protocol",
+	"gl_console_messages": ["console", "message"]
+}
# HG changeset patch
# User Jacob Vosmaer <jacob@gitlab.com>
# Date 1643115081 0
#      Tue Jan 25 12:51:21 2022 +0000
# Node ID 9d7d352114a8c1ba3f79d0b949fcbaec8c9ea061
# Parent  3b5a0ff21f311a0075abc54e810ea0f82945d627
Release 13.23.0

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v13.23.0
+
+- Add support for SSHUploadPackWithSidechannel RPC !557
+- Rate limiting documentation !556
+
 v13.22.2
 
 - Update to Ruby 2.7.5 !553
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.22.2
+13.23.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1643116085 0
#      Tue Jan 25 13:08:05 2022 +0000
# Node ID facb28d2103701af8b625f283d6387d863b51e2e
# Parent  3b5a0ff21f311a0075abc54e810ea0f82945d627
# Parent  9d7d352114a8c1ba3f79d0b949fcbaec8c9ea061
Merge branch 'jv-release-13.23.0' into 'main'

Release 13.23.0

See merge request gitlab-org/gitlab-shell!558

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v13.23.0
+
+- Add support for SSHUploadPackWithSidechannel RPC !557
+- Rate limiting documentation !556
+
 v13.22.2
 
 - Update to Ruby 2.7.5 !553
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.22.2
+13.23.0
# HG changeset patch
# User Lin Jen-Shin <jen-shin@gitlab.com>
# Date 1642178684 -28800
#      Sat Jan 15 00:44:44 2022 +0800
# Node ID 5b6fa84dcbcf6e2412f36b939d837ad392b09859
# Parent  facb28d2103701af8b625f283d6387d863b51e2e
Update bundler to 2.3.6

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -37,7 +37,7 @@
     - apt-get update -qq && apt-get install -y ruby ruby-dev
     - ruby -v
     - export PATH=~/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin
-    - gem install --force --bindir /usr/local/bin bundler -v 2.1.4
+    - gem install --force --bindir /usr/local/bin bundler -v 2.3.6
     - bundle install
     # Now set up to run the Golang tests
     - make build
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -23,4 +23,4 @@
   rspec (~> 3.8.0)
 
 BUNDLED WITH
-   2.1.4
+   2.3.6
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1643344168 0
#      Fri Jan 28 04:29:28 2022 +0000
# Node ID cbf3c23bde26c1c1cf09e4aa16c11f71e87c0f21
# Parent  facb28d2103701af8b625f283d6387d863b51e2e
# Parent  5b6fa84dcbcf6e2412f36b939d837ad392b09859
Merge branch 'update-bundler' into 'main'

Update bundler to 2.3.6 because 2.1.4 is too old

See merge request gitlab-org/gitlab-shell!554

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -37,7 +37,7 @@
     - apt-get update -qq && apt-get install -y ruby ruby-dev
     - ruby -v
     - export PATH=~/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin
-    - gem install --force --bindir /usr/local/bin bundler -v 2.1.4
+    - gem install --force --bindir /usr/local/bin bundler -v 2.3.6
     - bundle install
     # Now set up to run the Golang tests
     - make build
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -23,4 +23,4 @@
   rspec (~> 3.8.0)
 
 BUNDLED WITH
-   2.1.4
+   2.3.6
# HG changeset patch
# User Costel Maxim <cmaxim@gitlab.com>
# Date 1643368859 0
#      Fri Jan 28 11:20:59 2022 +0000
# Node ID 3fcba671aca940f27f12117db610bff5f2c4a27f
# Parent  facb28d2103701af8b625f283d6387d863b51e2e
Update link for gitlab-workhorse project location

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
 1. Call the GitLab Rails API to check if you are authorized, and what Gitaly server your repository is on
 1. Copy data back and forth between the SSH client and the Gitaly server
 
-If you access a GitLab server over HTTP(S) you end up in [gitlab-workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse).
+If you access a GitLab server over HTTP(S) you end up in [gitlab-workhorse](https://gitlab.com/gitlab-org/gitlab/tree/master/workhorse).
 
 An overview of the four cases described above:
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1643368859 0
#      Fri Jan 28 11:20:59 2022 +0000
# Node ID f11aa3c5289314273b0df987791c85de1a3bc87b
# Parent  cbf3c23bde26c1c1cf09e4aa16c11f71e87c0f21
# Parent  3fcba671aca940f27f12117db610bff5f2c4a27f
Merge branch 'cmaxim-main-patch-05177' into 'main'

Update link for gitlab-workhorse project location

See merge request gitlab-org/gitlab-shell!559

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
 1. Call the GitLab Rails API to check if you are authorized, and what Gitaly server your repository is on
 1. Copy data back and forth between the SSH client and the Gitaly server
 
-If you access a GitLab server over HTTP(S) you end up in [gitlab-workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse).
+If you access a GitLab server over HTTP(S) you end up in [gitlab-workhorse](https://gitlab.com/gitlab-org/gitlab/tree/master/workhorse).
 
 An overview of the four cases described above:
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1643399070 -10800
#      Fri Jan 28 22:44:30 2022 +0300
# Node ID b1e4e2817bfe8fe4f57940279134b0e2c9dec792
# Parent  f11aa3c5289314273b0df987791c85de1a3bc87b
Replace golang.org/x/crypto with gitlab-org/golang-crypto

This fork contains a fix for handling ssh-rsa public keys
of gitlab-sshd

Issue: https://gitlab.com/gitlab-org/gitlab-shell/-/issues/543

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -18,3 +18,5 @@
 	google.golang.org/grpc v1.38.0
 	gopkg.in/yaml.v2 v2.4.0
 )
+
+replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -820,12 +820,12 @@
 gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
 gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
 gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
-gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220118113436-b58be10dc69a h1:VeZYyHUa0zvnZwn5hQJMvetcnf+LFUcWJhsqlFsjkEA=
-gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220118113436-b58be10dc69a/go.mod h1:taZCYTkDDPh8LCetkdOEx/x+mkvNgGc9GIGlWkXivk0=
 gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490 h1:NzcVF6tscgsW890MQfVAhMVnAhXeohPaUGOTuFfIJXs=
 gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490/go.mod h1:taZCYTkDDPh8LCetkdOEx/x+mkvNgGc9GIGlWkXivk0=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80 h1:rVzTLlEDlX/ocWqNxeuxb9XklLzmI9FdP0teRqkSg00=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
@@ -865,33 +865,6 @@
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
 go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
 gocloud.dev v0.23.0/go.mod h1:zklCCIIo1N9ELkU2S2E7tW8P8eeMU7oGLeQCXdDwx9Q=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
-golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
-golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -947,7 +920,6 @@
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
@@ -986,8 +958,9 @@
 golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
 golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210505214959-0714010a04ed h1:V9kAVxLvz1lkufatrpHuUVyJ/5tR3Ms7rk951P4mI98=
 golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1028,7 +1001,6 @@
 golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1092,7 +1064,6 @@
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1643597794 0
#      Mon Jan 31 02:56:34 2022 +0000
# Node ID 5058e80bdd7220b4fd75c9aa3add4c0c5cf8e96c
# Parent  f11aa3c5289314273b0df987791c85de1a3bc87b
# Parent  b1e4e2817bfe8fe4f57940279134b0e2c9dec792
Merge branch 'id-add-gitlab-golang-crypto' into 'main'

Replace golang.org/x/crypto with gitlab-org/golang-crypto

See merge request gitlab-org/gitlab-shell!560

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -18,3 +18,5 @@
 	google.golang.org/grpc v1.38.0
 	gopkg.in/yaml.v2 v2.4.0
 )
+
+replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -820,12 +820,12 @@
 gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
 gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
 gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
-gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220118113436-b58be10dc69a h1:VeZYyHUa0zvnZwn5hQJMvetcnf+LFUcWJhsqlFsjkEA=
-gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220118113436-b58be10dc69a/go.mod h1:taZCYTkDDPh8LCetkdOEx/x+mkvNgGc9GIGlWkXivk0=
 gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490 h1:NzcVF6tscgsW890MQfVAhMVnAhXeohPaUGOTuFfIJXs=
 gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490/go.mod h1:taZCYTkDDPh8LCetkdOEx/x+mkvNgGc9GIGlWkXivk0=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80 h1:rVzTLlEDlX/ocWqNxeuxb9XklLzmI9FdP0teRqkSg00=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
@@ -865,33 +865,6 @@
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
 go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
 gocloud.dev v0.23.0/go.mod h1:zklCCIIo1N9ELkU2S2E7tW8P8eeMU7oGLeQCXdDwx9Q=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
-golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
-golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -947,7 +920,6 @@
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
@@ -986,8 +958,9 @@
 golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
 golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210505214959-0714010a04ed h1:V9kAVxLvz1lkufatrpHuUVyJ/5tR3Ms7rk951P4mI98=
 golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1028,7 +1001,6 @@
 golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1092,7 +1064,6 @@
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1643608702 -10800
#      Mon Jan 31 08:58:22 2022 +0300
# Node ID b9a33a7194ce666ef2bbd78b2387a2e7e000c786
# Parent  5058e80bdd7220b4fd75c9aa3add4c0c5cf8e96c
Release 13.23.1

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v13.23.1
+
+- Replace golang.org/x/crypto with gitlab-org/golang-crypto !560
+
 v13.23.0
 
 - Add support for SSHUploadPackWithSidechannel RPC !557
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.23.0
+13.23.1
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1643609167 0
#      Mon Jan 31 06:06:07 2022 +0000
# Node ID c980b46fd02fa3ba6ff9deae8ea6febd1f7e3692
# Parent  5058e80bdd7220b4fd75c9aa3add4c0c5cf8e96c
# Parent  b9a33a7194ce666ef2bbd78b2387a2e7e000c786
Merge branch 'id-release-new-tag' into 'main'

Release 13.23.1

See merge request gitlab-org/gitlab-shell!561

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v13.23.1
+
+- Replace golang.org/x/crypto with gitlab-org/golang-crypto !560
+
 v13.23.0
 
 - Add support for SSHUploadPackWithSidechannel RPC !557
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.23.0
+13.23.1
# HG changeset patch
# User Dustin Collins <714871-dustinmm80@users.noreply.gitlab.com>
# Date 1643677285 0
#      Tue Feb 01 01:01:25 2022 +0000
# Node ID 579aace524610f6a09af31f8018e2ef345263676
# Parent  c980b46fd02fa3ba6ff9deae8ea6febd1f7e3692
Update Go to version 1.17.6

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.16.12
+golang 1.17.6
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1643677285 0
#      Tue Feb 01 01:01:25 2022 +0000
# Node ID 764b23f3364d4d09e533af02d227c6735ac582da
# Parent  c980b46fd02fa3ba6ff9deae8ea6febd1f7e3692
# Parent  579aace524610f6a09af31f8018e2ef345263676
Merge branch '547-update-go-to-1176' into 'main'

Update Go to version 1.17.6

Closes #547

See merge request gitlab-org/gitlab-shell!562

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.16.12
+golang 1.17.6
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1643762913 -39600
#      Wed Feb 02 11:48:33 2022 +1100
# Node ID 1d419ff3814bb884456e3b2b8547054ffc7bc3ed
# Parent  764b23f3364d4d09e533af02d227c6735ac582da
Alpha sort .gitignore

diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -1,20 +1,20 @@
-config.yml
-cover.out
-tmp/*
-.idea
 *.log
 *.swp
-/*.log*
-authorized_keys.lock
+.bundle
+.bundle/
 .gitlab_shell_secret
-.bundle
-tags
-.bundle/
-custom_hooks
-hooks/*.d
-/go_build
-/bin/gitlab-sshd
+.idea
+/*.log*
+/bin/check
 /bin/gitlab-shell
 /bin/gitlab-shell-authorized-keys-check
 /bin/gitlab-shell-authorized-principals-check
-/bin/check
+/bin/gitlab-sshd
+/go_build
+authorized_keys.lock
+config.yml
+cover.out
+custom_hooks
+hooks/*.d
+tags
+tmp/*
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1643762928 -39600
#      Wed Feb 02 11:48:48 2022 +1100
# Node ID 1f269d721dbb7fb21f4bd8a9f9181c5f665cf27f
# Parent  1d419ff3814bb884456e3b2b8547054ffc7bc3ed
Ignore vendor directory

diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,4 @@
 hooks/*.d
 tags
 tmp/*
+vendor
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1643763633 -39600
#      Wed Feb 02 12:00:33 2022 +1100
# Node ID 7782d657e6cf7561d0bdd0947cc91713707a72f6
# Parent  1f269d721dbb7fb21f4bd8a9f9181c5f665cf27f
Ignore vendor/ directory even if present

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@
 VERSION_STRING := $(shell git describe --match v* 2>/dev/null || awk '$$0="v"$$0' VERSION 2>/dev/null || echo unknown)
 BUILD_TIME := $(shell date -u +%Y%m%d.%H%M%S)
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
-GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)"
+GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)" -mod=mod
 
 PREFIX ?= /usr/local
 
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1643782250 0
#      Wed Feb 02 06:10:50 2022 +0000
# Node ID e0329c98e29e2ace88a1cc3adf9675ef856d7df8
# Parent  764b23f3364d4d09e533af02d227c6735ac582da
# Parent  7782d657e6cf7561d0bdd0947cc91713707a72f6
Merge branch '539-problem-with-upgrading' into 'main'

When building, ignore the vendor/ directory if present

Closes #539

See merge request gitlab-org/gitlab-shell!565

diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -1,20 +1,21 @@
-config.yml
-cover.out
-tmp/*
-.idea
 *.log
 *.swp
-/*.log*
-authorized_keys.lock
+.bundle
+.bundle/
 .gitlab_shell_secret
-.bundle
-tags
-.bundle/
-custom_hooks
-hooks/*.d
-/go_build
-/bin/gitlab-sshd
+.idea
+/*.log*
+/bin/check
 /bin/gitlab-shell
 /bin/gitlab-shell-authorized-keys-check
 /bin/gitlab-shell-authorized-principals-check
-/bin/check
+/bin/gitlab-sshd
+/go_build
+authorized_keys.lock
+config.yml
+cover.out
+custom_hooks
+hooks/*.d
+tags
+tmp/*
+vendor
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@
 VERSION_STRING := $(shell git describe --match v* 2>/dev/null || awk '$$0="v"$$0' VERSION 2>/dev/null || echo unknown)
 BUILD_TIME := $(shell date -u +%Y%m%d.%H%M%S)
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
-GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)"
+GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)" -mod=mod
 
 PREFIX ?= /usr/local
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1643695105 -10800
#      Tue Feb 01 08:58:25 2022 +0300
# Node ID d7528d2ae08fbfc36b618727d94172dd98318529
# Parent  c980b46fd02fa3ba6ff9deae8ea6febd1f7e3692
Bump go-proxyproto package

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@
 	github.com/mattn/go-shellwords v1.0.11
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
-	github.com/pires/go-proxyproto v0.6.0
+	github.com/pires/go-proxyproto v0.6.1
 	github.com/prometheus/client_golang v1.10.0
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -669,8 +669,8 @@
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
 github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
-github.com/pires/go-proxyproto v0.6.0 h1:cLJUPnuQdiNf7P/wbeOKmM1khVdaMgTFDLj8h9ZrVYk=
-github.com/pires/go-proxyproto v0.6.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
+github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1643791996 0
#      Wed Feb 02 08:53:16 2022 +0000
# Node ID d706e036dece9dd2c04ea2f0adcbd54f241daa5f
# Parent  e0329c98e29e2ace88a1cc3adf9675ef856d7df8
# Parent  d7528d2ae08fbfc36b618727d94172dd98318529
Merge branch 'id-bump-go-proxyproto' into 'main'

Bump go-proxyproto package

See merge request gitlab-org/gitlab-shell!563

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@
 	github.com/mattn/go-shellwords v1.0.11
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
-	github.com/pires/go-proxyproto v0.6.0
+	github.com/pires/go-proxyproto v0.6.1
 	github.com/prometheus/client_golang v1.10.0
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -669,8 +669,8 @@
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
 github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
-github.com/pires/go-proxyproto v0.6.0 h1:cLJUPnuQdiNf7P/wbeOKmM1khVdaMgTFDLj8h9ZrVYk=
-github.com/pires/go-proxyproto v0.6.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
+github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1643704780 -10800
#      Tue Feb 01 11:39:40 2022 +0300
# Node ID 651e7a11b1acb1bd0826da249c28d2f497d5648a
# Parent  c980b46fd02fa3ba6ff9deae8ea6febd1f7e3692
Handle and log unhandled errors

Currently, we don't process the results of this execution,
because it's not really imprortant

Let's at least log the err if the execution went wrong

That will also make Vulnerability report happy

diff --git a/internal/command/twofactorrecover/twofactorrecover.go b/internal/command/twofactorrecover/twofactorrecover.go
--- a/internal/command/twofactorrecover/twofactorrecover.go
+++ b/internal/command/twofactorrecover/twofactorrecover.go
@@ -26,7 +26,7 @@
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.Debug("twofactorrecover: execute: Waiting for user input")
 
-	if c.canContinue() {
+	if c.getUserAnswer(ctx) == "yes" {
 		ctxlog.Debug("twofactorrecover: execute: User chose to continue")
 		c.displayRecoveryCodes(ctx)
 	} else {
@@ -37,16 +37,18 @@
 	return nil
 }
 
-func (c *Command) canContinue() bool {
+func (c *Command) getUserAnswer(ctx context.Context) string {
 	question :=
 		"Are you sure you want to generate new two-factor recovery codes?\n" +
 			"Any existing recovery codes you saved will be invalidated. (yes/no)"
 	fmt.Fprintln(c.ReadWriter.Out, question)
 
 	var answer string
-	fmt.Fscanln(io.LimitReader(c.ReadWriter.In, readerLimit), &answer)
+	if _, err := fmt.Fscanln(io.LimitReader(c.ReadWriter.In, readerLimit), &answer); err != nil {
+		log.ContextLogger(ctx).WithError(err).Debug("twofactorrecover: getUserAnswer: Failed to get user input")
+	}
 
-	return answer == "yes"
+	return answer
 }
 
 func (c *Command) displayRecoveryCodes(ctx context.Context) {
diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -22,7 +22,7 @@
 func (c *Command) Execute(ctx context.Context) error {
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.Info("twofactorverify: execute: waiting for user input")
-	otp := c.getOTP()
+	otp := c.getOTP(ctx)
 
 	ctxlog.Info("twofactorverify: execute: verifying entered OTP")
 	err := c.verifyOTP(ctx, otp)
@@ -35,14 +35,16 @@
 	return nil
 }
 
-func (c *Command) getOTP() string {
+func (c *Command) getOTP(ctx context.Context) string {
 	prompt := "OTP: "
 	fmt.Fprint(c.ReadWriter.Out, prompt)
 
 	var answer string
 	otpLength := int64(64)
 	reader := io.LimitReader(c.ReadWriter.In, otpLength)
-	fmt.Fscanln(reader, &answer)
+	if _, err := fmt.Fscanln(reader, &answer); err != nil {
+		log.ContextLogger(ctx).WithError(err).Debug("twofactorverify: getOTP: Failed to get user input")
+	}
 
 	return answer
 }
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -66,8 +66,11 @@
 		default:
 			// Ignore unknown requests but don't terminate the session
 			shouldContinue = true
+
 			if req.WantReply {
-				req.Reply(false, []byte{})
+				if err := req.Reply(false, []byte{}); err != nil {
+					sessionLog.WithError(err).Debug("session: handle: Failed to reply")
+				}
 			}
 		}
 
@@ -100,7 +103,9 @@
 	}
 
 	if req.WantReply {
-		req.Reply(accepted, []byte{})
+		if err := req.Reply(accepted, []byte{}); err != nil {
+			log.ContextLogger(ctx).WithError(err).Debug("session: handleEnv: Failed to reply")
+		}
 	}
 
 	log.WithContextFields(
@@ -124,8 +129,12 @@
 }
 
 func (s *session) handleShell(ctx context.Context, req *ssh.Request) uint32 {
+	ctxlog := log.ContextLogger(ctx)
+
 	if req.WantReply {
-		req.Reply(true, []byte{})
+		if err := req.Reply(true, []byte{}); err != nil {
+			ctxlog.WithError(err).Debug("session: handleShell: Failed to reply")
+		}
 	}
 
 	env := sshenv.Env{
@@ -151,7 +160,6 @@
 	}
 
 	cmdName := reflect.TypeOf(cmd).String()
-	ctxlog := log.ContextLogger(ctx)
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("session: handleShell: executing command")
 
 	if err := cmd.Execute(ctx); err != nil {
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1643858979 0
#      Thu Feb 03 03:29:39 2022 +0000
# Node ID b018dab8a79e595032abed594427df5b2dcc2f88
# Parent  d706e036dece9dd2c04ea2f0adcbd54f241daa5f
# Parent  651e7a11b1acb1bd0826da249c28d2f497d5648a
Merge branch 'id-handle-unhandled-errs' into 'main'

Handle and log unhandled errors

See merge request gitlab-org/gitlab-shell!564

diff --git a/internal/command/twofactorrecover/twofactorrecover.go b/internal/command/twofactorrecover/twofactorrecover.go
--- a/internal/command/twofactorrecover/twofactorrecover.go
+++ b/internal/command/twofactorrecover/twofactorrecover.go
@@ -26,7 +26,7 @@
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.Debug("twofactorrecover: execute: Waiting for user input")
 
-	if c.canContinue() {
+	if c.getUserAnswer(ctx) == "yes" {
 		ctxlog.Debug("twofactorrecover: execute: User chose to continue")
 		c.displayRecoveryCodes(ctx)
 	} else {
@@ -37,16 +37,18 @@
 	return nil
 }
 
-func (c *Command) canContinue() bool {
+func (c *Command) getUserAnswer(ctx context.Context) string {
 	question :=
 		"Are you sure you want to generate new two-factor recovery codes?\n" +
 			"Any existing recovery codes you saved will be invalidated. (yes/no)"
 	fmt.Fprintln(c.ReadWriter.Out, question)
 
 	var answer string
-	fmt.Fscanln(io.LimitReader(c.ReadWriter.In, readerLimit), &answer)
+	if _, err := fmt.Fscanln(io.LimitReader(c.ReadWriter.In, readerLimit), &answer); err != nil {
+		log.ContextLogger(ctx).WithError(err).Debug("twofactorrecover: getUserAnswer: Failed to get user input")
+	}
 
-	return answer == "yes"
+	return answer
 }
 
 func (c *Command) displayRecoveryCodes(ctx context.Context) {
diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -22,7 +22,7 @@
 func (c *Command) Execute(ctx context.Context) error {
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.Info("twofactorverify: execute: waiting for user input")
-	otp := c.getOTP()
+	otp := c.getOTP(ctx)
 
 	ctxlog.Info("twofactorverify: execute: verifying entered OTP")
 	err := c.verifyOTP(ctx, otp)
@@ -35,14 +35,16 @@
 	return nil
 }
 
-func (c *Command) getOTP() string {
+func (c *Command) getOTP(ctx context.Context) string {
 	prompt := "OTP: "
 	fmt.Fprint(c.ReadWriter.Out, prompt)
 
 	var answer string
 	otpLength := int64(64)
 	reader := io.LimitReader(c.ReadWriter.In, otpLength)
-	fmt.Fscanln(reader, &answer)
+	if _, err := fmt.Fscanln(reader, &answer); err != nil {
+		log.ContextLogger(ctx).WithError(err).Debug("twofactorverify: getOTP: Failed to get user input")
+	}
 
 	return answer
 }
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -66,8 +66,11 @@
 		default:
 			// Ignore unknown requests but don't terminate the session
 			shouldContinue = true
+
 			if req.WantReply {
-				req.Reply(false, []byte{})
+				if err := req.Reply(false, []byte{}); err != nil {
+					sessionLog.WithError(err).Debug("session: handle: Failed to reply")
+				}
 			}
 		}
 
@@ -100,7 +103,9 @@
 	}
 
 	if req.WantReply {
-		req.Reply(accepted, []byte{})
+		if err := req.Reply(accepted, []byte{}); err != nil {
+			log.ContextLogger(ctx).WithError(err).Debug("session: handleEnv: Failed to reply")
+		}
 	}
 
 	log.WithContextFields(
@@ -124,8 +129,12 @@
 }
 
 func (s *session) handleShell(ctx context.Context, req *ssh.Request) uint32 {
+	ctxlog := log.ContextLogger(ctx)
+
 	if req.WantReply {
-		req.Reply(true, []byte{})
+		if err := req.Reply(true, []byte{}); err != nil {
+			ctxlog.WithError(err).Debug("session: handleShell: Failed to reply")
+		}
 	}
 
 	env := sshenv.Env{
@@ -151,7 +160,6 @@
 	}
 
 	cmdName := reflect.TypeOf(cmd).String()
-	ctxlog := log.ContextLogger(ctx)
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("session: handleShell: executing command")
 
 	if err := cmd.Execute(ctx); err != nil {
# HG changeset patch
# User Sean Carroll <scarroll@gitlab.com>
# Date 1643835576 0
#      Wed Feb 02 20:59:36 2022 +0000
# Node ID 3ee55a99af18602455e86ca82ccfe41a44c40786
# Parent  d706e036dece9dd2c04ea2f0adcbd54f241daa5f
Update LICENSE to 2022

diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2011-2018 GitLab B.V.
+Copyright (c) 2011-2022 GitLab B.V.
 
 With regard to the GitLab Software:
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1643871009 0
#      Thu Feb 03 06:50:09 2022 +0000
# Node ID ec9cb207066e44ae038f29d35766d7a42a6f6c97
# Parent  b018dab8a79e595032abed594427df5b2dcc2f88
# Parent  3ee55a99af18602455e86ca82ccfe41a44c40786
Merge branch 'sean_carroll-main-patch-51330' into 'main'

Update LICENSE to 2022

See merge request gitlab-org/gitlab-shell!566

diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2011-2018 GitLab B.V.
+Copyright (c) 2011-2022 GitLab B.V.
 
 With regard to the GitLab Software:
 
# HG changeset patch
# User Dan Rhodes <drhodes@gitlab.com>
# Date 1643897533 0
#      Thu Feb 03 14:12:13 2022 +0000
# Node ID e3dcb762bb25f717f38fe358aaa043e0036e1491
# Parent  ec9cb207066e44ae038f29d35766d7a42a6f6c97
Add title and correct c.right notice to license

diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
-Copyright (c) 2011-2022 GitLab B.V.
+MIT License
 
-With regard to the GitLab Software:
+Copyright (c) 2011-present GitLab B.V.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -9,17 +9,13 @@
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
-For all third party components incorporated into the GitLab Software, those
-components are licensed under the original license provided by the owner of the
-applicable component.
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1643900695 0
#      Thu Feb 03 15:04:55 2022 +0000
# Node ID 8ce047910bdc4933916b05a9c63eeaee4351c744
# Parent  ec9cb207066e44ae038f29d35766d7a42a6f6c97
# Parent  e3dcb762bb25f717f38fe358aaa043e0036e1491
Merge branch 'dfrhodes-main-patch-08696' into 'main'

Add title and correct copyright notice to license

See merge request gitlab-org/gitlab-shell!568

diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
-Copyright (c) 2011-2022 GitLab B.V.
+MIT License
 
-With regard to the GitLab Software:
+Copyright (c) 2011-present GitLab B.V.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -9,17 +9,13 @@
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
-For all third party components incorporated into the GitLab Software, those
-components are licensed under the original license provided by the owner of the
-applicable component.
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1644526720 -10800
#      Thu Feb 10 23:58:40 2022 +0300
# Node ID b25ff657093910467ba52a6de806b3945ff9ad53
# Parent  8ce047910bdc4933916b05a9c63eeaee4351c744
Bump labkit version to 1.12.0

It also bumps minor versions of its dependencies

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -9,13 +9,13 @@
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.1
-	github.com/prometheus/client_golang v1.10.0
+	github.com/prometheus/client_golang v1.11.0
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490
-	gitlab.com/gitlab-org/labkit v1.7.0
+	gitlab.com/gitlab-org/labkit v1.12.0
 	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	google.golang.org/grpc v1.38.0
+	google.golang.org/grpc v1.40.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -21,8 +21,13 @@
 cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
-cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
 cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
+cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
+cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
+cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
+cloud.google.com/go v0.92.2 h1:podK44+0gcW5rWGMjJiPH0+rzkCTQx/zT0qF5CLqVkM=
+cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -32,6 +37,8 @@
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo=
+cloud.google.com/go/profiler v0.1.0 h1:MG/rxKC1MztRfEWMGYKFISxyZak5hNh29f0A/z2tvWk=
+cloud.google.com/go/profiler v0.1.0/go.mod h1:D7S7LV/zKbRWkOzYL1b5xytpqt8Ikd/v/yvf1/Tx2pQ=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@@ -44,6 +51,8 @@
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 cloud.google.com/go/storage v1.15.0 h1:Ljj+ZXVEhCr/1+4ZhvtteN1ND7UUsNTlduGclLh8GO0=
 cloud.google.com/go/storage v1.15.0/go.mod h1:mjjQMoxxyGH7Jr8K5qrx6N2O0AHsczI61sMNn03GIZI=
+cloud.google.com/go/trace v0.1.0 h1:nUGUK79FOkN0UGUXhBmVBkbu1PYsHe0YyFSPLOD9Npg=
+cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g=
 contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4=
@@ -88,17 +97,22 @@
 github.com/DataDog/datadog-go v4.4.0+incompatible h1:R7WqXWP4fIOAqWJtUKmSfuc7eDsBT58k9AY5WSHVosk=
 github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
 github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
+github.com/DataDog/sketches-go v1.0.0 h1:chm5KSXO7kO+ywGWJ0Zs6tdmWU8PBXSbywFVciL6BG4=
+github.com/DataDog/sketches-go v1.0.0/go.mod h1:O+XkJHWk9w4hDwY2ZUDU31ZC9sNYlYo8DiFsxjYeo1k=
 github.com/GoogleCloudPlatform/cloudsql-proxy v1.22.0/go.mod h1:mAm5O/zik2RFmcpigNjg6nMotDL8ZXJaxKzgGVcSMFA=
-github.com/HdrHistogram/hdrhistogram-go v1.1.0 h1:6dpdDPTRoo78HxAJ6T1HfMiKSnqhgRRqzCuPshRkQ7I=
 github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
+github.com/HdrHistogram/hdrhistogram-go v1.1.1 h1:cJXY5VLMHgejurPjZH6Fo9rIwRGLefBGdiaENZALqrg=
+github.com/HdrHistogram/hdrhistogram-go v1.1.1/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
 github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
-github.com/Microsoft/go-winio v0.4.19 h1:ZMZG0O5M8bhD0lgCURV8yu3hQ7TGvQ4L1ZW8+J0j9iE=
 github.com/Microsoft/go-winio v0.4.19/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
+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/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
@@ -116,6 +130,7 @@
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/alexbrainman/sspi v0.0.0-20180125232955-4729b3d4d858/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -145,6 +160,9 @@
 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=
 github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
+github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@@ -161,6 +179,7 @@
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
@@ -212,6 +231,7 @@
 github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
 github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
@@ -233,6 +253,7 @@
 github.com/getsentry/sentry-go v0.5.1/go.mod h1:B8H7x8TYDPkeWPRzGpIiFO97LZP6rL8A3hEt8lUItMw=
 github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
 github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
+github.com/getsentry/sentry-go v0.11.0/go.mod h1:KBQIxiZAetw62Cj8Ri964vAEWVdgfaUCn30Q3bCvANo=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
@@ -310,8 +331,9 @@
 github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
 github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -345,16 +367,20 @@
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/go-replayers/grpcreplay v1.0.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
 github.com/google/go-replayers/httpreplay v0.1.2/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -367,8 +393,12 @@
 github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210125172800-10e9aeb4a998/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210804190019-f964ff605595 h1:uNrRgpnKjTfxu4qHaZAAs3eKTYV1EzGF3dAykpnxgDE=
+github.com/google/pprof v0.0.0-20210804190019-f964ff605595/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
@@ -396,6 +426,7 @@
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
 github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -495,8 +526,8 @@
 github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jteeuwen/go-bindata v3.0.8-0.20180305030458-6025e8de665b+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
@@ -559,8 +590,9 @@
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7/go.mod h1:Spd59icnvRxSKuyijbbwe5AemzvcyXAUBgApa7VybMw=
 github.com/lightstep/lightstep-tracer-go v0.15.6/go.mod h1:6AMpwZpsyCFwSovxzM78e+AsYxE8sGwiM6C3TytaWeI=
 github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/lightstep/lightstep-tracer-go v0.24.0 h1:qGUbkzHP64NA9r+uIbCvf303IzHPr0M4JlkaDMxXqqk=
 github.com/lightstep/lightstep-tracer-go v0.24.0/go.mod h1:RnONwHKg89zYPmF+Uig5PpHMUcYCFgml8+r4SS53y7A=
+github.com/lightstep/lightstep-tracer-go v0.25.0 h1:sGVnz8h3jTQuHKMbUe2949nXm3Sg09N1UcR3VoQNN5E=
+github.com/lightstep/lightstep-tracer-go v0.25.0/go.mod h1:G1ZAEaqTHFPWpWunnbUn1ADEY/Jvzz7jIOaXwAfD6A8=
 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -663,8 +695,9 @@
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
-github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
 github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
@@ -685,8 +718,9 @@
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg=
 github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
+github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -698,8 +732,9 @@
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y=
 github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
+github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -709,6 +744,7 @@
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
@@ -733,6 +769,8 @@
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY=
 github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/shirou/gopsutil/v3 v3.21.2 h1:fIOk3hyqV1oGKogfGNjUZa0lUbtlkx3+ZT0IoJth2uM=
+github.com/shirou/gopsutil/v3 v3.21.2/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
 github.com/shogo82148/go-shuffle v0.0.0-20170808115208-59829097ff3b/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
 github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
@@ -750,6 +788,7 @@
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
@@ -779,11 +818,16 @@
 github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
 github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=
+github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
+github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc=
+github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
 github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-client-go v2.27.0+incompatible h1:6WVONolFJiB8Vx9bq4z9ddyV/SXSpfvvtb7Yl/TGHiE=
 github.com/uber/jaeger-client-go v2.27.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
+github.com/uber/jaeger-client-go v2.29.1+incompatible h1:R9ec3zO3sGpzs0abd43Y+fBZRJ9uiH6lXyR/+u6brW4=
+github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
 github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
 github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
 github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
@@ -815,6 +859,7 @@
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
 gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
@@ -831,8 +876,8 @@
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
 gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
 gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
-gitlab.com/gitlab-org/labkit v1.7.0 h1:ufG42cvJ+VkqaUWVEoat/h9PGcW9FFSUPQFz7uwL5wc=
-gitlab.com/gitlab-org/labkit v1.7.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
+gitlab.com/gitlab-org/labkit v1.12.0 h1:XVwGqXfHrhEr2bzZbYy/eA7xulAeyvk9HB3Q7nH2H4I=
+gitlab.com/gitlab-org/labkit v1.12.0/go.mod h1:tnvyKiPF/Y0MeBy+ACYWRWexOEQk6PTRGUc9GstcnXc=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
@@ -847,6 +892,7 @@
 go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@@ -893,8 +939,9 @@
 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=
 golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
@@ -905,7 +952,6 @@
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -956,6 +1002,7 @@
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -974,8 +1021,11 @@
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c h1:SgVl/sCtkicsS7psKkje4H9YtjdEl3xsYh7N+5TDHqY=
 golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a h1:4Kd8OPUx1xgUwrHDaviWZO8MsgoZTZYC3g+8m16RBww=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1048,6 +1098,7 @@
 golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1055,13 +1106,21 @@
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
@@ -1146,8 +1205,13 @@
 golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1186,8 +1250,13 @@
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
 google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA=
-google.golang.org/api v0.46.0 h1:jkDWHOBIoNSD0OQpq4rtBVu+Rh325MPjXG1rakAp8JU=
 google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
+google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
+google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
+google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
+google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
+google.golang.org/api v0.54.0 h1:ECJUVngj71QI6XEm7b1sAf8BljU5inEhMbKPR8Lxhhk=
+google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1225,6 +1294,7 @@
 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@@ -1247,8 +1317,18 @@
 google.golang.org/genproto v0.0.0-20210420162539-3c870d7478d2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2 h1:pl8qT5D+48655f14yDURpIZwSPvMWuuekfAP+gxtjvk=
 google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c h1:iLQakcwWG3k/++1q/46apVb1sUQ3IqIdn9yUE6eh/xA=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
 google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -1269,14 +1349,20 @@
 google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
 google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
+google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1288,11 +1374,13 @@
 google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/DataDog/dd-trace-go.v1 v1.7.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
-gopkg.in/DataDog/dd-trace-go.v1 v1.30.0 h1:yJJrDYzAlUsDPpAVBjv4VFnXKTbgvaJFTX0646xDPi4=
 gopkg.in/DataDog/dd-trace-go.v1 v1.30.0/go.mod h1:SnKViq44dv/0gjl9RpkP0Y2G3BJSRkp6eYdCSu39iI8=
+gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 h1:DkD0plWEVUB8v/Ru6kRBW30Hy/fRNBC8hPdcExuBZMc=
+gopkg.in/DataDog/dd-trace-go.v1 v1.32.0/go.mod h1:wRKMf/tRASHwH/UOfPQ3IQmVFhTz2/1a1/mpXoIjF54=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -1324,6 +1412,7 @@
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1644549365 0
#      Fri Feb 11 03:16:05 2022 +0000
# Node ID b028fb8e0da292d66af6578395a6b03fd094be38
# Parent  8ce047910bdc4933916b05a9c63eeaee4351c744
# Parent  b25ff657093910467ba52a6de806b3945ff9ad53
Merge branch 'id-update-labkit-version' into 'main'

Bump labkit version to 1.12.0

See merge request gitlab-org/gitlab-shell!569

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -9,13 +9,13 @@
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.1
-	github.com/prometheus/client_golang v1.10.0
+	github.com/prometheus/client_golang v1.11.0
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490
-	gitlab.com/gitlab-org/labkit v1.7.0
+	gitlab.com/gitlab-org/labkit v1.12.0
 	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	google.golang.org/grpc v1.38.0
+	google.golang.org/grpc v1.40.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -21,8 +21,13 @@
 cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
-cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
 cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
+cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
+cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
+cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
+cloud.google.com/go v0.92.2 h1:podK44+0gcW5rWGMjJiPH0+rzkCTQx/zT0qF5CLqVkM=
+cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -32,6 +37,8 @@
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo=
+cloud.google.com/go/profiler v0.1.0 h1:MG/rxKC1MztRfEWMGYKFISxyZak5hNh29f0A/z2tvWk=
+cloud.google.com/go/profiler v0.1.0/go.mod h1:D7S7LV/zKbRWkOzYL1b5xytpqt8Ikd/v/yvf1/Tx2pQ=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@@ -44,6 +51,8 @@
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 cloud.google.com/go/storage v1.15.0 h1:Ljj+ZXVEhCr/1+4ZhvtteN1ND7UUsNTlduGclLh8GO0=
 cloud.google.com/go/storage v1.15.0/go.mod h1:mjjQMoxxyGH7Jr8K5qrx6N2O0AHsczI61sMNn03GIZI=
+cloud.google.com/go/trace v0.1.0 h1:nUGUK79FOkN0UGUXhBmVBkbu1PYsHe0YyFSPLOD9Npg=
+cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g=
 contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4=
@@ -88,17 +97,22 @@
 github.com/DataDog/datadog-go v4.4.0+incompatible h1:R7WqXWP4fIOAqWJtUKmSfuc7eDsBT58k9AY5WSHVosk=
 github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
 github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
+github.com/DataDog/sketches-go v1.0.0 h1:chm5KSXO7kO+ywGWJ0Zs6tdmWU8PBXSbywFVciL6BG4=
+github.com/DataDog/sketches-go v1.0.0/go.mod h1:O+XkJHWk9w4hDwY2ZUDU31ZC9sNYlYo8DiFsxjYeo1k=
 github.com/GoogleCloudPlatform/cloudsql-proxy v1.22.0/go.mod h1:mAm5O/zik2RFmcpigNjg6nMotDL8ZXJaxKzgGVcSMFA=
-github.com/HdrHistogram/hdrhistogram-go v1.1.0 h1:6dpdDPTRoo78HxAJ6T1HfMiKSnqhgRRqzCuPshRkQ7I=
 github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
+github.com/HdrHistogram/hdrhistogram-go v1.1.1 h1:cJXY5VLMHgejurPjZH6Fo9rIwRGLefBGdiaENZALqrg=
+github.com/HdrHistogram/hdrhistogram-go v1.1.1/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
 github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
-github.com/Microsoft/go-winio v0.4.19 h1:ZMZG0O5M8bhD0lgCURV8yu3hQ7TGvQ4L1ZW8+J0j9iE=
 github.com/Microsoft/go-winio v0.4.19/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
+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/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
@@ -116,6 +130,7 @@
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/alexbrainman/sspi v0.0.0-20180125232955-4729b3d4d858/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -145,6 +160,9 @@
 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=
 github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
+github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@@ -161,6 +179,7 @@
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
@@ -212,6 +231,7 @@
 github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
 github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
@@ -233,6 +253,7 @@
 github.com/getsentry/sentry-go v0.5.1/go.mod h1:B8H7x8TYDPkeWPRzGpIiFO97LZP6rL8A3hEt8lUItMw=
 github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
 github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
+github.com/getsentry/sentry-go v0.11.0/go.mod h1:KBQIxiZAetw62Cj8Ri964vAEWVdgfaUCn30Q3bCvANo=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
@@ -310,8 +331,9 @@
 github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
 github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -345,16 +367,20 @@
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/go-replayers/grpcreplay v1.0.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
 github.com/google/go-replayers/httpreplay v0.1.2/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -367,8 +393,12 @@
 github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210125172800-10e9aeb4a998/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210804190019-f964ff605595 h1:uNrRgpnKjTfxu4qHaZAAs3eKTYV1EzGF3dAykpnxgDE=
+github.com/google/pprof v0.0.0-20210804190019-f964ff605595/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
@@ -396,6 +426,7 @@
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
 github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -495,8 +526,8 @@
 github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jteeuwen/go-bindata v3.0.8-0.20180305030458-6025e8de665b+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
@@ -559,8 +590,9 @@
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7/go.mod h1:Spd59icnvRxSKuyijbbwe5AemzvcyXAUBgApa7VybMw=
 github.com/lightstep/lightstep-tracer-go v0.15.6/go.mod h1:6AMpwZpsyCFwSovxzM78e+AsYxE8sGwiM6C3TytaWeI=
 github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/lightstep/lightstep-tracer-go v0.24.0 h1:qGUbkzHP64NA9r+uIbCvf303IzHPr0M4JlkaDMxXqqk=
 github.com/lightstep/lightstep-tracer-go v0.24.0/go.mod h1:RnONwHKg89zYPmF+Uig5PpHMUcYCFgml8+r4SS53y7A=
+github.com/lightstep/lightstep-tracer-go v0.25.0 h1:sGVnz8h3jTQuHKMbUe2949nXm3Sg09N1UcR3VoQNN5E=
+github.com/lightstep/lightstep-tracer-go v0.25.0/go.mod h1:G1ZAEaqTHFPWpWunnbUn1ADEY/Jvzz7jIOaXwAfD6A8=
 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -663,8 +695,9 @@
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
-github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
 github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
@@ -685,8 +718,9 @@
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg=
 github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
+github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -698,8 +732,9 @@
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y=
 github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
+github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -709,6 +744,7 @@
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
@@ -733,6 +769,8 @@
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY=
 github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/shirou/gopsutil/v3 v3.21.2 h1:fIOk3hyqV1oGKogfGNjUZa0lUbtlkx3+ZT0IoJth2uM=
+github.com/shirou/gopsutil/v3 v3.21.2/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
 github.com/shogo82148/go-shuffle v0.0.0-20170808115208-59829097ff3b/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
 github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
@@ -750,6 +788,7 @@
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
@@ -779,11 +818,16 @@
 github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
 github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=
+github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
+github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc=
+github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
 github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-client-go v2.27.0+incompatible h1:6WVONolFJiB8Vx9bq4z9ddyV/SXSpfvvtb7Yl/TGHiE=
 github.com/uber/jaeger-client-go v2.27.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
+github.com/uber/jaeger-client-go v2.29.1+incompatible h1:R9ec3zO3sGpzs0abd43Y+fBZRJ9uiH6lXyR/+u6brW4=
+github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
 github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
 github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
 github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
@@ -815,6 +859,7 @@
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
 gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
@@ -831,8 +876,8 @@
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
 gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
 gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
-gitlab.com/gitlab-org/labkit v1.7.0 h1:ufG42cvJ+VkqaUWVEoat/h9PGcW9FFSUPQFz7uwL5wc=
-gitlab.com/gitlab-org/labkit v1.7.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
+gitlab.com/gitlab-org/labkit v1.12.0 h1:XVwGqXfHrhEr2bzZbYy/eA7xulAeyvk9HB3Q7nH2H4I=
+gitlab.com/gitlab-org/labkit v1.12.0/go.mod h1:tnvyKiPF/Y0MeBy+ACYWRWexOEQk6PTRGUc9GstcnXc=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
@@ -847,6 +892,7 @@
 go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@@ -893,8 +939,9 @@
 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=
 golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
@@ -905,7 +952,6 @@
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -956,6 +1002,7 @@
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -974,8 +1021,11 @@
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c h1:SgVl/sCtkicsS7psKkje4H9YtjdEl3xsYh7N+5TDHqY=
 golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a h1:4Kd8OPUx1xgUwrHDaviWZO8MsgoZTZYC3g+8m16RBww=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1048,6 +1098,7 @@
 golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1055,13 +1106,21 @@
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
@@ -1146,8 +1205,13 @@
 golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1186,8 +1250,13 @@
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
 google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA=
-google.golang.org/api v0.46.0 h1:jkDWHOBIoNSD0OQpq4rtBVu+Rh325MPjXG1rakAp8JU=
 google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
+google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
+google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
+google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
+google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
+google.golang.org/api v0.54.0 h1:ECJUVngj71QI6XEm7b1sAf8BljU5inEhMbKPR8Lxhhk=
+google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1225,6 +1294,7 @@
 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@@ -1247,8 +1317,18 @@
 google.golang.org/genproto v0.0.0-20210420162539-3c870d7478d2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2 h1:pl8qT5D+48655f14yDURpIZwSPvMWuuekfAP+gxtjvk=
 google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c h1:iLQakcwWG3k/++1q/46apVb1sUQ3IqIdn9yUE6eh/xA=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
 google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -1269,14 +1349,20 @@
 google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
 google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
+google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1288,11 +1374,13 @@
 google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/DataDog/dd-trace-go.v1 v1.7.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
-gopkg.in/DataDog/dd-trace-go.v1 v1.30.0 h1:yJJrDYzAlUsDPpAVBjv4VFnXKTbgvaJFTX0646xDPi4=
 gopkg.in/DataDog/dd-trace-go.v1 v1.30.0/go.mod h1:SnKViq44dv/0gjl9RpkP0Y2G3BJSRkp6eYdCSu39iI8=
+gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 h1:DkD0plWEVUB8v/Ru6kRBW30Hy/fRNBC8hPdcExuBZMc=
+gopkg.in/DataDog/dd-trace-go.v1 v1.32.0/go.mod h1:wRKMf/tRASHwH/UOfPQ3IQmVFhTz2/1a1/mpXoIjF54=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -1324,6 +1412,7 @@
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1644557128 -10800
#      Fri Feb 11 08:25:28 2022 +0300
# Node ID 4d0cb80af195a05eb5f019f57ad00be260db925c
# Parent  b028fb8e0da292d66af6578395a6b03fd094be38
Release 13.23.2

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,10 @@
+v13.23.2
+
+- Bump labkit version to 1.12.0 !569
+- Add title and correct copyright notice to license !568
+- Bump go-proxyproto package !563
+- Update Go to version 1.17.6 !562
+
 v13.23.1
 
 - Replace golang.org/x/crypto with gitlab-org/golang-crypto !560
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.23.1
+13.23.2
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1644557600 0
#      Fri Feb 11 05:33:20 2022 +0000
# Node ID 7298cb147cee1f199041abc6e9d7649d369e4164
# Parent  b028fb8e0da292d66af6578395a6b03fd094be38
# Parent  4d0cb80af195a05eb5f019f57ad00be260db925c
Merge branch 'id-release-13-23-2' into 'main'

Release 13.23.2

See merge request gitlab-org/gitlab-shell!570

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,10 @@
+v13.23.2
+
+- Bump labkit version to 1.12.0 !569
+- Add title and correct copyright notice to license !568
+- Bump go-proxyproto package !563
+- Update Go to version 1.17.6 !562
+
 v13.23.1
 
 - Replace golang.org/x/crypto with gitlab-org/golang-crypto !560
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.23.1
+13.23.2
# HG changeset patch
# User Amy Qualls <aqualls@gitlab.com>
# Date 1644941285 0
#      Tue Feb 15 16:08:05 2022 +0000
# Node ID d4783460ff560256ff4008674064e88134689cb1
# Parent  7298cb147cee1f199041abc6e9d7649d369e4164
Add aqualls as codeowner for docs files

diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -1,1 +1,4 @@
 * @ashmckenzie @igor.drozdov @nick.thomas @patrickbajao
+
+[Documentation]
+*.md @aqualls
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1644941286 0
#      Tue Feb 15 16:08:06 2022 +0000
# Node ID fb67d0891763f5844078fee22094149cfe162725
# Parent  7298cb147cee1f199041abc6e9d7649d369e4164
# Parent  d4783460ff560256ff4008674064e88134689cb1
Merge branch 'aqualls-codeowner-addition' into 'main'

Add aqualls as codeowner for docs files

See merge request gitlab-org/gitlab-shell!573

diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -1,1 +1,4 @@
 * @ashmckenzie @igor.drozdov @nick.thomas @patrickbajao
+
+[Documentation]
+*.md @aqualls
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1644916972 -10800
#      Tue Feb 15 12:22:52 2022 +0300
# Node ID b8965ff7051e334de43515156b0141cb7fdb6e7b
# Parent  7298cb147cee1f199041abc6e9d7649d369e4164
Add docs for full feature list

Describe what Gitlab Shell is capable of

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -18,6 +18,8 @@
 1. git pull over SSH -> gitlab-shell -> API call to gitlab-rails (Authorization) -> accept or decline -> establish Gitaly session
 1. git push over SSH -> gitlab-shell (git command is not executed yet) -> establish Gitaly session -> (in Gitaly) gitlab-shell pre-receive hook -> API call to gitlab-rails (authorization) -> accept or decline push
 
+[Full feature list](/doc/features.md)
+
 ## Code status
 
 [![pipeline status](https://gitlab.com/gitlab-org/gitlab-shell/badges/main/pipeline.svg)](https://gitlab.com/gitlab-org/gitlab-shell/-/pipelines?ref=main)
@@ -94,10 +96,6 @@
 tests requiring Gitaly will be skipped. They will always run in the CI
 environment.
 
-## Git LFS
-
-Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
-
 ## Logging Guidelines
 
 In general, it should be possible to determine the structure, but not content,
diff --git a/doc/features.md b/doc/features.md
new file mode 100644
--- /dev/null
+++ b/doc/features.md
@@ -0,0 +1,69 @@
+### Discover
+
+Allows users to identify themselves on an instance via SSH. The command is useful for checking out quickly whether a user has SSH access to the instance:
+
+```bash
+ssh git@<hostname>
+
+PTY allocation request failed on channel 0
+Welcome to GitLab, @username!
+Connection to staging.gitlab.com closed.
+```
+
+When permission is denied:
+
+```bash
+ssh git@<hostname>
+git@<hostname>: Permission denied (publickey).
+```
+
+### Git operations
+
+Gitlab Shell provides support for Git operations over SSH via processing `git-upload-pack`, `git-receive-pack` and `git-upload-archive` SSH commands. It limit the set of commands to predefined git commands (git push, git clone/pull, git archive).
+
+### Generate new 2FA recovery codes
+
+Allows users to [generate new 2FA recovery codes](https://docs.gitlab.com/ee/user/profile/account/two_factor_authentication.html#generate-new-recovery-codes-using-ssh).
+
+```bash
+ssh git@<hostname> 2fa_recovery_codes
+Are you sure you want to generate new two-factor recovery codes?
+Any existing recovery codes you saved will be invalidated. (yes/no)
+yes
+
+Your two-factor authentication recovery codes are:
+...
+```
+
+### Verify 2FA OTP
+
+Allows users to [verify their 2FA OTP](https://docs.gitlab.com/ee/security/two_factor_authentication.html#2fa-for-git-over-ssh-operations).
+
+```bash
+ssh git@<hostname> 2fa_verify
+OTP: 347419
+
+OTP validation failed.
+```
+
+### LFS authentication
+
+Allows users to generate credentials for LFS authentication.
+
+```bash
+ssh git@<hostname> git-lfs-authenticate <project-path> <upload/download>
+
+{"header":{"Authorization":"Basic ..."},"href":"https://gitlab.com/user/project.git/info/lfs","expires_in":7200}
+```
+
+### Personal access token
+
+Allows users to personal access tokens via SSH
+
+```bash
+ssh git@<hostname> personal_access_token <name> <scope1[,scope2,...]> [ttl_days]
+
+Token:   glpat-...
+Scopes:  api
+Expires: 2022-02-05
+```
# HG changeset patch
# User Amy Qualls <aqualls@gitlab.com>
# Date 1644940151 28800
#      Tue Feb 15 07:49:11 2022 -0800
# Node ID 917d7140aefce92a7b5df2d641471c2f132c7ce2
# Parent  b8965ff7051e334de43515156b0141cb7fdb6e7b
Fix link in README

Shouldn't start with a leading slash.

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
 1. git pull over SSH -> gitlab-shell -> API call to gitlab-rails (Authorization) -> accept or decline -> establish Gitaly session
 1. git push over SSH -> gitlab-shell (git command is not executed yet) -> establish Gitaly session -> (in Gitaly) gitlab-shell pre-receive hook -> API call to gitlab-rails (authorization) -> accept or decline push
 
-[Full feature list](/doc/features.md)
+[Full feature list](doc/features.md)
 
 ## Code status
 
@@ -115,7 +115,7 @@
 
 GitLab Shell performs rate-limiting by user account and project for git operations. GitLab Shell accepts git operation requests and then makes a call to the Rails rate-limiter (backed by Redis). If the `user + project` exceeds the rate limit then GitLab Shell will then drop further connection requests for that `user + project`.
 
-The rate-limiter is applied at the git command (plumbing) level. Each command has a rate limit of 600/minute. For example, `git push` has 600/minute and `git pull` has another 600/minute. 
+The rate-limiter is applied at the git command (plumbing) level. Each command has a rate limit of 600/minute. For example, `git push` has 600/minute and `git pull` has another 600/minute.
 
 Because they are using the same plumbing command `git-upload-pack`, `git pull` and `git clone` are in effect the same command for the purposes of rate-limiting.
 
# HG changeset patch
# User Amy Qualls <aqualls@gitlab.com>
# Date 1644940170 28800
#      Tue Feb 15 07:49:30 2022 -0800
# Node ID 5ed6fda2a66f36a8943a0aec6f488dcc2d018b09
# Parent  917d7140aefce92a7b5df2d641471c2f132c7ce2
Revisions for tone and style

Do an initial cleanup pass:

- Line wraps
- Change 'allows' to 'enables'
- Clean up syntax highlighting
- Fix spelling and capitalization
- Standardize on colons at the end of the introductory sentences

diff --git a/doc/features.md b/doc/features.md
--- a/doc/features.md
+++ b/doc/features.md
@@ -1,8 +1,11 @@
-### Discover
+# Feature list
+
+## Discover
 
-Allows users to identify themselves on an instance via SSH. The command is useful for checking out quickly whether a user has SSH access to the instance:
+Allows users to identify themselves on an instance via SSH. The command helps to
+confirm quickly whether a user has SSH access to the instance:
 
-```bash
+```shell
 ssh git@<hostname>
 
 PTY allocation request failed on channel 0
@@ -10,22 +13,30 @@
 Connection to staging.gitlab.com closed.
 ```
 
-When permission is denied:
+When permission is denied, it returns:
 
-```bash
+```shell
 ssh git@<hostname>
 git@<hostname>: Permission denied (publickey).
 ```
 
-### Git operations
+## Git operations
 
-Gitlab Shell provides support for Git operations over SSH via processing `git-upload-pack`, `git-receive-pack` and `git-upload-archive` SSH commands. It limit the set of commands to predefined git commands (git push, git clone/pull, git archive).
+GitLab Shell provides support for Git operations over SSH by processing
+`git-upload-pack`, `git-receive-pack` and `git-upload-archive` SSH commands.
+It limits the set of commands to predefined Git commands:
 
-### Generate new 2FA recovery codes
+- `git archive`
+- `git clone`
+- `git pull`
+- `git push`
 
-Allows users to [generate new 2FA recovery codes](https://docs.gitlab.com/ee/user/profile/account/two_factor_authentication.html#generate-new-recovery-codes-using-ssh).
+## Generate new 2FA recovery codes
 
-```bash
+Enables users to
+[generate new 2FA recovery codes](https://docs.gitlab.com/ee/user/profile/account/two_factor_authentication.html#generate-new-recovery-codes-using-ssh):
+
+```plaintext
 ssh git@<hostname> 2fa_recovery_codes
 Are you sure you want to generate new two-factor recovery codes?
 Any existing recovery codes you saved will be invalidated. (yes/no)
@@ -35,32 +46,33 @@
 ...
 ```
 
-### Verify 2FA OTP
+## Verify 2FA OTP
 
-Allows users to [verify their 2FA OTP](https://docs.gitlab.com/ee/security/two_factor_authentication.html#2fa-for-git-over-ssh-operations).
+Allows users to verify their
+[2FA one-time password (OTP)](https://docs.gitlab.com/ee/security/two_factor_authentication.html#2fa-for-git-over-ssh-operations):
 
-```bash
+```shell
 ssh git@<hostname> 2fa_verify
 OTP: 347419
 
 OTP validation failed.
 ```
 
-### LFS authentication
+## LFS authentication
 
-Allows users to generate credentials for LFS authentication.
+Enables users to generate credentials for LFS authentication:
 
-```bash
+```shell
 ssh git@<hostname> git-lfs-authenticate <project-path> <upload/download>
 
 {"header":{"Authorization":"Basic ..."},"href":"https://gitlab.com/user/project.git/info/lfs","expires_in":7200}
 ```
 
-### Personal access token
+## Personal access token
 
-Allows users to personal access tokens via SSH
+Enables users to use personal access tokens via SSH:
 
-```bash
+```shell
 ssh git@<hostname> personal_access_token <name> <scope1[,scope2,...]> [ttl_days]
 
 Token:   glpat-...
# HG changeset patch
# User Amy Qualls <aqualls@gitlab.com>
# Date 1644940300 28800
#      Tue Feb 15 07:51:40 2022 -0800
# Node ID 679b2636650368f6dd11f00c9a89c887212076af
# Parent  5ed6fda2a66f36a8943a0aec6f488dcc2d018b09
Add metadata for these pages

These pages should get credited to Source Code. (I should also
check about CODEOWNERS entries too.)

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
 # GitLab Shell
 
 ## GitLab Shell handles git SSH sessions for GitLab
diff --git a/doc/features.md b/doc/features.md
--- a/doc/features.md
+++ b/doc/features.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
 # Feature list
 
 ## Discover
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1644986027 0
#      Wed Feb 16 04:33:47 2022 +0000
# Node ID f9a29a150ebc159cbe4ad1d32e2be2adc8adbfd4
# Parent  fb67d0891763f5844078fee22094149cfe162725
# Parent  679b2636650368f6dd11f00c9a89c887212076af
Merge branch 'id-add-features-description' into 'main'

Add docs for full feature list

See merge request gitlab-org/gitlab-shell!571

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
 # GitLab Shell
 
 ## GitLab Shell handles git SSH sessions for GitLab
@@ -18,6 +24,8 @@
 1. git pull over SSH -> gitlab-shell -> API call to gitlab-rails (Authorization) -> accept or decline -> establish Gitaly session
 1. git push over SSH -> gitlab-shell (git command is not executed yet) -> establish Gitaly session -> (in Gitaly) gitlab-shell pre-receive hook -> API call to gitlab-rails (authorization) -> accept or decline push
 
+[Full feature list](doc/features.md)
+
 ## Code status
 
 [![pipeline status](https://gitlab.com/gitlab-org/gitlab-shell/badges/main/pipeline.svg)](https://gitlab.com/gitlab-org/gitlab-shell/-/pipelines?ref=main)
@@ -94,10 +102,6 @@
 tests requiring Gitaly will be skipped. They will always run in the CI
 environment.
 
-## Git LFS
-
-Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
-
 ## Logging Guidelines
 
 In general, it should be possible to determine the structure, but not content,
@@ -117,7 +121,7 @@
 
 GitLab Shell performs rate-limiting by user account and project for git operations. GitLab Shell accepts git operation requests and then makes a call to the Rails rate-limiter (backed by Redis). If the `user + project` exceeds the rate limit then GitLab Shell will then drop further connection requests for that `user + project`.
 
-The rate-limiter is applied at the git command (plumbing) level. Each command has a rate limit of 600/minute. For example, `git push` has 600/minute and `git pull` has another 600/minute. 
+The rate-limiter is applied at the git command (plumbing) level. Each command has a rate limit of 600/minute. For example, `git push` has 600/minute and `git pull` has another 600/minute.
 
 Because they are using the same plumbing command `git-upload-pack`, `git pull` and `git clone` are in effect the same command for the purposes of rate-limiting.
 
diff --git a/doc/features.md b/doc/features.md
new file mode 100644
--- /dev/null
+++ b/doc/features.md
@@ -0,0 +1,87 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Feature list
+
+## Discover
+
+Allows users to identify themselves on an instance via SSH. The command helps to
+confirm quickly whether a user has SSH access to the instance:
+
+```shell
+ssh git@<hostname>
+
+PTY allocation request failed on channel 0
+Welcome to GitLab, @username!
+Connection to staging.gitlab.com closed.
+```
+
+When permission is denied, it returns:
+
+```shell
+ssh git@<hostname>
+git@<hostname>: Permission denied (publickey).
+```
+
+## Git operations
+
+GitLab Shell provides support for Git operations over SSH by processing
+`git-upload-pack`, `git-receive-pack` and `git-upload-archive` SSH commands.
+It limits the set of commands to predefined Git commands:
+
+- `git archive`
+- `git clone`
+- `git pull`
+- `git push`
+
+## Generate new 2FA recovery codes
+
+Enables users to
+[generate new 2FA recovery codes](https://docs.gitlab.com/ee/user/profile/account/two_factor_authentication.html#generate-new-recovery-codes-using-ssh):
+
+```plaintext
+ssh git@<hostname> 2fa_recovery_codes
+Are you sure you want to generate new two-factor recovery codes?
+Any existing recovery codes you saved will be invalidated. (yes/no)
+yes
+
+Your two-factor authentication recovery codes are:
+...
+```
+
+## Verify 2FA OTP
+
+Allows users to verify their
+[2FA one-time password (OTP)](https://docs.gitlab.com/ee/security/two_factor_authentication.html#2fa-for-git-over-ssh-operations):
+
+```shell
+ssh git@<hostname> 2fa_verify
+OTP: 347419
+
+OTP validation failed.
+```
+
+## LFS authentication
+
+Enables users to generate credentials for LFS authentication:
+
+```shell
+ssh git@<hostname> git-lfs-authenticate <project-path> <upload/download>
+
+{"header":{"Authorization":"Basic ..."},"href":"https://gitlab.com/user/project.git/info/lfs","expires_in":7200}
+```
+
+## Personal access token
+
+Enables users to use personal access tokens via SSH:
+
+```shell
+ssh git@<hostname> personal_access_token <name> <scope1[,scope2,...]> [ttl_days]
+
+Token:   glpat-...
+Scopes:  api
+Expires: 2022-02-05
+```
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1644917812 -10800
#      Tue Feb 15 12:36:52 2022 +0300
# Node ID 61382697d85cbf0241d0e014812af72bec72042d
# Parent  f9a29a150ebc159cbe4ad1d32e2be2adc8adbfd4
Move code guidelines to doc/beginners_guide.md

It helps to have less verbose README.md

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -42,81 +42,6 @@
 We follow the [Golang Release Policy](https://golang.org/doc/devel/release.html#policy)
 of supporting the current stable version and the previous two major versions.
 
-## Check
-
-Checks if GitLab API access and redis via internal API can be reached:
-
-    make check
-
-## Compile
-
-Builds the `gitlab-shell` binaries, placing them into `bin/`.
-
-    make compile
-
-## Install
-
-Builds the `gitlab-shell` binaries and installs them onto the filesystem. The
-default location is `/usr/local`, but can be controlled by use of the `PREFIX`
-and `DESTDIR` environment variables.
-
-    make install
-
-## Setup
-
-This command is intended for use when installing GitLab from source on a single
-machine. In addition to compiling the gitlab-shell binaries, it ensures that
-various paths on the filesystem exist with the correct permissions. Do not run
-it unless instructed to by your installation method documentation.
-
-    make setup
-
-
-## Testing
-
-Run tests:
-
-    bundle install
-    make test
-
-Run gofmt:
-
-    make verify
-
-Run both test and verify (the default Makefile target):
-
-    bundle install
-    make validate
-
-### Gitaly
-
-Some tests need a Gitaly server. The
-[`docker-compose.yml`](./docker-compose.yml) file will run Gitaly on
-port 8075. To tell the tests where Gitaly is, set
-`GITALY_CONNECTION_INFO`:
-
-    export GITALY_CONNECTION_INFO='{"address": "tcp://localhost:8075", "storage": "default"}'
-    make test
-
-If no `GITALY_CONNECTION_INFO` is set, the test suite will still run, but any
-tests requiring Gitaly will be skipped. They will always run in the CI
-environment.
-
-## Logging Guidelines
-
-In general, it should be possible to determine the structure, but not content,
-of a gitlab-shell or gitlab-sshd session just from inspecting the logs. Some
-guidelines:
-
-- We use [`gitlab.com/gitlab-org/labkit/log`](https://pkg.go.dev/gitlab.com/gitlab-org/labkit/log)
-  for logging functionality
-- **Always** include a correlation ID
-- Log messages should be invariant and unique. Include accessory information in
-  fields, using `log.WithField`, `log.WithFields`, or `log.WithError`.
-- Log success cases as well as error cases
-- Logging too much is better than not logging enough. If a message seems too
-  verbose, consider reducing the log level before removing the message.
-
 ## Rate Limiting
 
 GitLab Shell performs rate-limiting by user account and project for git operations. GitLab Shell accepts git operation requests and then makes a call to the Rails rate-limiter (backed by Redis). If the `user + project` exceeds the rate limit then GitLab Shell will then drop further connection requests for that `user + project`.
@@ -133,7 +58,8 @@
 
 ## Contributing
 
-See [CONTRIBUTING.md](./CONTRIBUTING.md).
+- See [CONTRIBUTING.md](./CONTRIBUTING.md).
+- See the [beginner's guide](doc/beginners_guide.md).
 
 ## License
 
diff --git a/doc/beginners_guide.md b/doc/beginners_guide.md
new file mode 100644
--- /dev/null
+++ b/doc/beginners_guide.md
@@ -0,0 +1,76 @@
+## Beginner's guide to Gitlab Shell contributions
+
+### Check
+
+Checks if GitLab API access and redis via internal API can be reached:
+
+    make check
+
+### Compile
+
+Builds the `gitlab-shell` binaries, placing them into `bin/`.
+
+    make compile
+
+### Install
+
+Builds the `gitlab-shell` binaries and installs them onto the filesystem. The
+default location is `/usr/local`, but can be controlled by use of the `PREFIX`
+and `DESTDIR` environment variables.
+
+    make install
+
+### Setup
+
+This command is intended for use when installing GitLab from source on a single
+machine. In addition to compiling the gitlab-shell binaries, it ensures that
+various paths on the filesystem exist with the correct permissions. Do not run
+it unless instructed to by your installation method documentation.
+
+    make setup
+
+
+### Testing
+
+Run tests:
+
+    bundle install
+    make test
+
+Run gofmt:
+
+    make verify
+
+Run both test and verify (the default Makefile target):
+
+    bundle install
+    make validate
+
+### Gitaly
+
+Some tests need a Gitaly server. The
+[`docker-compose.yml`](./docker-compose.yml) file will run Gitaly on
+port 8075. To tell the tests where Gitaly is, set
+`GITALY_CONNECTION_INFO`:
+
+    export GITALY_CONNECTION_INFO='{"address": "tcp://localhost:8075", "storage": "default"}'
+    make test
+
+If no `GITALY_CONNECTION_INFO` is set, the test suite will still run, but any
+tests requiring Gitaly will be skipped. They will always run in the CI
+environment.
+
+### Logging Guidelines
+
+In general, it should be possible to determine the structure, but not content,
+of a gitlab-shell or gitlab-sshd session just from inspecting the logs. Some
+guidelines:
+
+- We use [`gitlab.com/gitlab-org/labkit/log`](https://pkg.go.dev/gitlab.com/gitlab-org/labkit/log)
+  for logging functionality
+- **Always** include a correlation ID
+- Log messages should be invariant and unique. Include accessory information in
+  fields, using `log.WithField`, `log.WithFields`, or `log.WithError`.
+- Log success cases as well as error cases
+- Logging too much is better than not logging enough. If a message seems too
+  verbose, consider reducing the log level before removing the message.
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1645025533 0
#      Wed Feb 16 15:32:13 2022 +0000
# Node ID 265cd8bc4277717dc0bfec08fbe33ab90dee4605
# Parent  61382697d85cbf0241d0e014812af72bec72042d
Add metadata to page

diff --git a/doc/beginners_guide.md b/doc/beginners_guide.md
--- a/doc/beginners_guide.md
+++ b/doc/beginners_guide.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
 ## Beginner's guide to Gitlab Shell contributions
 
 ### Check
# HG changeset patch
# User Amy Qualls <aqualls@gitlab.com>
# Date 1645026786 28800
#      Wed Feb 16 07:53:06 2022 -0800
# Node ID 61e323eceabe34bd463aa02724763c0e63f2cf17
# Parent  265cd8bc4277717dc0bfec08fbe33ab90dee4605
First pass of tone and style polish

Bring the page a lot closer to GitLab tone and style. Not perfect,
but we've gotta start somewhere.

diff --git a/doc/beginners_guide.md b/doc/beginners_guide.md
--- a/doc/beginners_guide.md
+++ b/doc/beginners_guide.md
@@ -4,79 +4,91 @@
 info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
 ---
 
-## Beginner's guide to Gitlab Shell contributions
+# Beginner's guide to GitLab Shell contributions
 
-### Check
+## Check
+
+Checks if GitLab API access and Redis via internal API can be reached:
 
-Checks if GitLab API access and redis via internal API can be reached:
+```shell
+make check
+```
 
-    make check
-
-### Compile
+## Compile
 
 Builds the `gitlab-shell` binaries, placing them into `bin/`.
 
-    make compile
+```shell
+make compile
+```
 
-### Install
+## Install
 
-Builds the `gitlab-shell` binaries and installs them onto the filesystem. The
-default location is `/usr/local`, but can be controlled by use of the `PREFIX`
+Builds the `gitlab-shell` binaries and installs them onto the file system. The
+default location is `/usr/local`, but you can change the location by setting the `PREFIX`
 and `DESTDIR` environment variables.
 
-    make install
+```shell
+make install
+```
 
-### Setup
+## Setup
 
 This command is intended for use when installing GitLab from source on a single
-machine. In addition to compiling the gitlab-shell binaries, it ensures that
-various paths on the filesystem exist with the correct permissions. Do not run
-it unless instructed to by your installation method documentation.
+machine. It compiles the GitLab Shell binaries, and ensures that
+various paths on the file system exist with the correct permissions. Do not run
+this command unless your installation method documentation instructs you to.
 
-    make setup
+```shell
+make setup
+```
 
-
-### Testing
+## Testing
 
 Run tests:
 
-    bundle install
-    make test
+```shell
+bundle install
+make test
+```
 
-Run gofmt:
+Run Gofmt:
 
-    make verify
+```shell
+make verify
+```
 
 Run both test and verify (the default Makefile target):
 
-    bundle install
-    make validate
+```shell
+bundle install
+make validate
+```
 
-### Gitaly
+## Gitaly
 
 Some tests need a Gitaly server. The
-[`docker-compose.yml`](./docker-compose.yml) file will run Gitaly on
-port 8075. To tell the tests where Gitaly is, set
-`GITALY_CONNECTION_INFO`:
+[`docker-compose.yml`](../docker-compose.yml) file runs Gitaly on port 8075.
+To tell the tests where Gitaly is, set `GITALY_CONNECTION_INFO`:
 
-    export GITALY_CONNECTION_INFO='{"address": "tcp://localhost:8075", "storage": "default"}'
-    make test
+```plaintext
+export GITALY_CONNECTION_INFO='{"address": "tcp://localhost:8075", "storage": "default"}'
+make test
+```
 
-If no `GITALY_CONNECTION_INFO` is set, the test suite will still run, but any
-tests requiring Gitaly will be skipped. They will always run in the CI
-environment.
+If no `GITALY_CONNECTION_INFO` is set, the test suite still runs, but any
+tests requiring Gitaly are skipped. The tests always run in the CI environment.
 
-### Logging Guidelines
+## Logging Guidelines
 
-In general, it should be possible to determine the structure, but not content,
-of a gitlab-shell or gitlab-sshd session just from inspecting the logs. Some
-guidelines:
+In general, you can determine the structure, but not content, of a GitLab Shell
+or `gitlab-sshd` session by inspecting the logs. Some guidelines:
 
 - We use [`gitlab.com/gitlab-org/labkit/log`](https://pkg.go.dev/gitlab.com/gitlab-org/labkit/log)
-  for logging functionality
-- **Always** include a correlation ID
+  for logging.
+- Always include a correlation ID.
 - Log messages should be invariant and unique. Include accessory information in
   fields, using `log.WithField`, `log.WithFields`, or `log.WithError`.
-- Log success cases as well as error cases
+- Log both success cases and error cases.
 - Logging too much is better than not logging enough. If a message seems too
   verbose, consider reducing the log level before removing the message.
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1645071804 0
#      Thu Feb 17 04:23:24 2022 +0000
# Node ID 6ea91f81fdce307685c6d4455a6717d3e9f8c3e6
# Parent  f9a29a150ebc159cbe4ad1d32e2be2adc8adbfd4
# Parent  61e323eceabe34bd463aa02724763c0e63f2cf17
Merge branch 'id-create-beginners-guide' into 'main'

Move code guidelines to doc/beginners_guide.md

See merge request gitlab-org/gitlab-shell!572

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -42,81 +42,6 @@
 We follow the [Golang Release Policy](https://golang.org/doc/devel/release.html#policy)
 of supporting the current stable version and the previous two major versions.
 
-## Check
-
-Checks if GitLab API access and redis via internal API can be reached:
-
-    make check
-
-## Compile
-
-Builds the `gitlab-shell` binaries, placing them into `bin/`.
-
-    make compile
-
-## Install
-
-Builds the `gitlab-shell` binaries and installs them onto the filesystem. The
-default location is `/usr/local`, but can be controlled by use of the `PREFIX`
-and `DESTDIR` environment variables.
-
-    make install
-
-## Setup
-
-This command is intended for use when installing GitLab from source on a single
-machine. In addition to compiling the gitlab-shell binaries, it ensures that
-various paths on the filesystem exist with the correct permissions. Do not run
-it unless instructed to by your installation method documentation.
-
-    make setup
-
-
-## Testing
-
-Run tests:
-
-    bundle install
-    make test
-
-Run gofmt:
-
-    make verify
-
-Run both test and verify (the default Makefile target):
-
-    bundle install
-    make validate
-
-### Gitaly
-
-Some tests need a Gitaly server. The
-[`docker-compose.yml`](./docker-compose.yml) file will run Gitaly on
-port 8075. To tell the tests where Gitaly is, set
-`GITALY_CONNECTION_INFO`:
-
-    export GITALY_CONNECTION_INFO='{"address": "tcp://localhost:8075", "storage": "default"}'
-    make test
-
-If no `GITALY_CONNECTION_INFO` is set, the test suite will still run, but any
-tests requiring Gitaly will be skipped. They will always run in the CI
-environment.
-
-## Logging Guidelines
-
-In general, it should be possible to determine the structure, but not content,
-of a gitlab-shell or gitlab-sshd session just from inspecting the logs. Some
-guidelines:
-
-- We use [`gitlab.com/gitlab-org/labkit/log`](https://pkg.go.dev/gitlab.com/gitlab-org/labkit/log)
-  for logging functionality
-- **Always** include a correlation ID
-- Log messages should be invariant and unique. Include accessory information in
-  fields, using `log.WithField`, `log.WithFields`, or `log.WithError`.
-- Log success cases as well as error cases
-- Logging too much is better than not logging enough. If a message seems too
-  verbose, consider reducing the log level before removing the message.
-
 ## Rate Limiting
 
 GitLab Shell performs rate-limiting by user account and project for git operations. GitLab Shell accepts git operation requests and then makes a call to the Rails rate-limiter (backed by Redis). If the `user + project` exceeds the rate limit then GitLab Shell will then drop further connection requests for that `user + project`.
@@ -133,7 +58,8 @@
 
 ## Contributing
 
-See [CONTRIBUTING.md](./CONTRIBUTING.md).
+- See [CONTRIBUTING.md](./CONTRIBUTING.md).
+- See the [beginner's guide](doc/beginners_guide.md).
 
 ## License
 
diff --git a/doc/beginners_guide.md b/doc/beginners_guide.md
new file mode 100644
--- /dev/null
+++ b/doc/beginners_guide.md
@@ -0,0 +1,94 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Beginner's guide to GitLab Shell contributions
+
+## Check
+
+Checks if GitLab API access and Redis via internal API can be reached:
+
+```shell
+make check
+```
+
+## Compile
+
+Builds the `gitlab-shell` binaries, placing them into `bin/`.
+
+```shell
+make compile
+```
+
+## Install
+
+Builds the `gitlab-shell` binaries and installs them onto the file system. The
+default location is `/usr/local`, but you can change the location by setting the `PREFIX`
+and `DESTDIR` environment variables.
+
+```shell
+make install
+```
+
+## Setup
+
+This command is intended for use when installing GitLab from source on a single
+machine. It compiles the GitLab Shell binaries, and ensures that
+various paths on the file system exist with the correct permissions. Do not run
+this command unless your installation method documentation instructs you to.
+
+```shell
+make setup
+```
+
+## Testing
+
+Run tests:
+
+```shell
+bundle install
+make test
+```
+
+Run Gofmt:
+
+```shell
+make verify
+```
+
+Run both test and verify (the default Makefile target):
+
+```shell
+bundle install
+make validate
+```
+
+## Gitaly
+
+Some tests need a Gitaly server. The
+[`docker-compose.yml`](../docker-compose.yml) file runs Gitaly on port 8075.
+To tell the tests where Gitaly is, set `GITALY_CONNECTION_INFO`:
+
+```plaintext
+export GITALY_CONNECTION_INFO='{"address": "tcp://localhost:8075", "storage": "default"}'
+make test
+```
+
+If no `GITALY_CONNECTION_INFO` is set, the test suite still runs, but any
+tests requiring Gitaly are skipped. The tests always run in the CI environment.
+
+## Logging Guidelines
+
+In general, you can determine the structure, but not content, of a GitLab Shell
+or `gitlab-sshd` session by inspecting the logs. Some guidelines:
+
+- We use [`gitlab.com/gitlab-org/labkit/log`](https://pkg.go.dev/gitlab.com/gitlab-org/labkit/log)
+  for logging.
+- Always include a correlation ID.
+- Log messages should be invariant and unique. Include accessory information in
+  fields, using `log.WithField`, `log.WithFields`, or `log.WithError`.
+- Log both success cases and error cases.
+- Logging too much is better than not logging enough. If a message seems too
+  verbose, consider reducing the log level before removing the message.
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1645029164 -10800
#      Wed Feb 16 19:32:44 2022 +0300
# Node ID 5d134925d763e644f7aea3a09bb2e682df8c4dce
# Parent  f9a29a150ebc159cbe4ad1d32e2be2adc8adbfd4
Add more metrics for gitlab-sshd

- Counter of HTTP merge requests
- Gauge of HTTP requests currently being performed
- Gauge of connections currently handled by gitlab sshd

diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -9,7 +9,6 @@
 	"sync"
 	"time"
 
-	"github.com/prometheus/client_golang/prometheus/promhttp"
 	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
@@ -114,7 +113,7 @@
 		}
 
 		tr := client.Transport
-		client.Transport = promhttp.InstrumentRoundTripperDuration(metrics.HttpRequestDuration, tr)
+		client.Transport = metrics.NewRoundTripper(tr)
 
 		c.httpClient = client
 	})
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -25,7 +25,7 @@
 	require.Equal(t, "foo", os.Getenv("SSL_CERT_DIR"))
 }
 
-func TestHttpClient(t *testing.T) {
+func TestCustomPrometheusMetrics(t *testing.T) {
 	url := testserver.StartHttpServer(t, []testserver.TestRequestHandler{})
 
 	config := &Config{GitlabUrl: url}
@@ -38,14 +38,19 @@
 	ms, err := prometheus.DefaultGatherer.Gather()
 	require.NoError(t, err)
 
-	lastMetric := ms[0]
-	require.Equal(t, lastMetric.GetName(), "gitlab_shell_http_request_seconds")
-
-	labels := lastMetric.GetMetric()[0].Label
+	var actualNames []string
+	for _, m := range ms[0:6] {
+		actualNames = append(actualNames, m.GetName())
+	}
 
-	require.Equal(t, "code", labels[0].GetName())
-	require.Equal(t, "404", labels[0].GetValue())
+	expectedMetricNames := []string{
+		"gitlab_shell_http_in_flight_requests",
+		"gitlab_shell_http_request_duration_seconds",
+		"gitlab_shell_http_requests_total",
+		"gitlab_shell_sshd_concurrent_limited_sessions_total",
+		"gitlab_shell_sshd_connection_duration_seconds",
+		"gitlab_shell_sshd_in_flight_connections",
+	}
 
-	require.Equal(t, "method", labels[1].GetName())
-	require.Equal(t, "get", labels[1].GetValue())
+	require.Equal(t, expectedMetricNames, actualNames)
 }
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -1,14 +1,25 @@
 package metrics
 
 import (
+	"net/http"
+
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/client_golang/prometheus/promauto"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
 )
 
 const (
 	namespace     = "gitlab_shell"
 	sshdSubsystem = "sshd"
 	httpSubsystem = "http"
+
+	httpInFlightRequestsMetricName       = "in_flight_requests"
+	httpRequestsTotalMetricName          = "requests_total"
+	httpRequestDurationSecondsMetricName = "request_duration_seconds"
+
+	sshdConnectionsInFlightName = "in_flight_connections"
+	sshdConnectionDuration      = "connection_duration_seconds"
+	sshdHitMaxSessions          = "concurrent_limited_sessions_total"
 )
 
 var (
@@ -16,7 +27,7 @@
 		prometheus.HistogramOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      "connection_duration_seconds",
+			Name:      sshdConnectionDuration,
 			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
 			Buckets: []float64{
 				0.005, /* 5ms */
@@ -32,32 +43,72 @@
 		},
 	)
 
+	SshdConnectionsInFlight = promauto.NewGauge(
+		prometheus.GaugeOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      sshdConnectionsInFlightName,
+			Help:      "A gauge of connections currently being served by gitlab-shell sshd.",
+		},
+	)
+
 	SshdHitMaxSessions = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      "concurrent_limited_sessions_total",
+			Name:      sshdHitMaxSessions,
 			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
 		},
 	)
 
-	HttpRequestDuration = promauto.NewHistogramVec(
+	// The metrics and the buckets size are similar to the ones we have for handlers in Labkit
+	// When the MR: https://gitlab.com/gitlab-org/labkit/-/merge_requests/150 is merged,
+	// these metrics can be refactored out of Gitlab Shell code by using the helper function from Labkit
+	httpRequestsTotal = promauto.NewCounterVec(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: httpSubsystem,
+			Name:      httpRequestsTotalMetricName,
+			Help:      "A counter for http requests.",
+		},
+		[]string{"code", "method"},
+	)
+
+	httpRequestDurationSeconds = promauto.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Namespace: namespace,
 			Subsystem: httpSubsystem,
-			Name:      "request_seconds",
-			Help:      "A histogram of latencies for gitlab-shell http requests",
+			Name:      httpRequestDurationSecondsMetricName,
+			Help:      "A histogram of latencies for http requests.",
 			Buckets: []float64{
-				0.01, /* 10ms */
-				0.05, /* 50ms */
-				0.1,  /* 100ms */
-				0.25, /* 250ms */
-				0.5,  /* 500ms */
-				1.0,  /* 1s */
-				5.0,  /* 5s */
-				10.0, /* 10s */
+				0.005, /* 5ms */
+				0.025, /* 25ms */
+				0.1,   /* 100ms */
+				0.5,   /* 500ms */
+				1.0,   /* 1s */
+				10.0,  /* 10s */
+				30.0,  /* 30s */
+				60.0,  /* 1m */
+				300.0, /* 5m */
 			},
 		},
 		[]string{"code", "method"},
 	)
+
+	httpInFlightRequests = promauto.NewGauge(
+		prometheus.GaugeOpts{
+			Namespace: namespace,
+			Subsystem: httpSubsystem,
+			Name:      httpInFlightRequestsMetricName,
+			Help:      "A gauge of requests currently being performed.",
+		},
+	)
 )
+
+func NewRoundTripper(next http.RoundTripper) promhttp.RoundTripperFunc {
+	rt := next
+
+	rt = promhttp.InstrumentRoundTripperCounter(httpRequestsTotal, rt)
+	rt = promhttp.InstrumentRoundTripperDuration(httpRequestDurationSeconds, rt)
+	return promhttp.InstrumentRoundTripperInFlight(httpInFlightRequests, rt)
+}
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -31,6 +31,9 @@
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
+	metrics.SshdConnectionsInFlight.Inc()
+	defer metrics.SshdConnectionsInFlight.Dec()
+
 	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
 
 	for newChannel := range chans {
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1645501214 0
#      Tue Feb 22 03:40:14 2022 +0000
# Node ID f93b7e6dd81a3ff0cef31d2621913110588e857a
# Parent  6ea91f81fdce307685c6d4455a6717d3e9f8c3e6
# Parent  5d134925d763e644f7aea3a09bb2e682df8c4dce
Merge branch 'id-concurrent-connections-prometheus' into 'main'

Add more metrics for gitlab-sshd

See merge request gitlab-org/gitlab-shell!574

diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -9,7 +9,6 @@
 	"sync"
 	"time"
 
-	"github.com/prometheus/client_golang/prometheus/promhttp"
 	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
@@ -114,7 +113,7 @@
 		}
 
 		tr := client.Transport
-		client.Transport = promhttp.InstrumentRoundTripperDuration(metrics.HttpRequestDuration, tr)
+		client.Transport = metrics.NewRoundTripper(tr)
 
 		c.httpClient = client
 	})
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -25,7 +25,7 @@
 	require.Equal(t, "foo", os.Getenv("SSL_CERT_DIR"))
 }
 
-func TestHttpClient(t *testing.T) {
+func TestCustomPrometheusMetrics(t *testing.T) {
 	url := testserver.StartHttpServer(t, []testserver.TestRequestHandler{})
 
 	config := &Config{GitlabUrl: url}
@@ -38,14 +38,19 @@
 	ms, err := prometheus.DefaultGatherer.Gather()
 	require.NoError(t, err)
 
-	lastMetric := ms[0]
-	require.Equal(t, lastMetric.GetName(), "gitlab_shell_http_request_seconds")
-
-	labels := lastMetric.GetMetric()[0].Label
+	var actualNames []string
+	for _, m := range ms[0:6] {
+		actualNames = append(actualNames, m.GetName())
+	}
 
-	require.Equal(t, "code", labels[0].GetName())
-	require.Equal(t, "404", labels[0].GetValue())
+	expectedMetricNames := []string{
+		"gitlab_shell_http_in_flight_requests",
+		"gitlab_shell_http_request_duration_seconds",
+		"gitlab_shell_http_requests_total",
+		"gitlab_shell_sshd_concurrent_limited_sessions_total",
+		"gitlab_shell_sshd_connection_duration_seconds",
+		"gitlab_shell_sshd_in_flight_connections",
+	}
 
-	require.Equal(t, "method", labels[1].GetName())
-	require.Equal(t, "get", labels[1].GetValue())
+	require.Equal(t, expectedMetricNames, actualNames)
 }
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -1,14 +1,25 @@
 package metrics
 
 import (
+	"net/http"
+
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/client_golang/prometheus/promauto"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
 )
 
 const (
 	namespace     = "gitlab_shell"
 	sshdSubsystem = "sshd"
 	httpSubsystem = "http"
+
+	httpInFlightRequestsMetricName       = "in_flight_requests"
+	httpRequestsTotalMetricName          = "requests_total"
+	httpRequestDurationSecondsMetricName = "request_duration_seconds"
+
+	sshdConnectionsInFlightName = "in_flight_connections"
+	sshdConnectionDuration      = "connection_duration_seconds"
+	sshdHitMaxSessions          = "concurrent_limited_sessions_total"
 )
 
 var (
@@ -16,7 +27,7 @@
 		prometheus.HistogramOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      "connection_duration_seconds",
+			Name:      sshdConnectionDuration,
 			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
 			Buckets: []float64{
 				0.005, /* 5ms */
@@ -32,32 +43,72 @@
 		},
 	)
 
+	SshdConnectionsInFlight = promauto.NewGauge(
+		prometheus.GaugeOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      sshdConnectionsInFlightName,
+			Help:      "A gauge of connections currently being served by gitlab-shell sshd.",
+		},
+	)
+
 	SshdHitMaxSessions = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      "concurrent_limited_sessions_total",
+			Name:      sshdHitMaxSessions,
 			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
 		},
 	)
 
-	HttpRequestDuration = promauto.NewHistogramVec(
+	// The metrics and the buckets size are similar to the ones we have for handlers in Labkit
+	// When the MR: https://gitlab.com/gitlab-org/labkit/-/merge_requests/150 is merged,
+	// these metrics can be refactored out of Gitlab Shell code by using the helper function from Labkit
+	httpRequestsTotal = promauto.NewCounterVec(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: httpSubsystem,
+			Name:      httpRequestsTotalMetricName,
+			Help:      "A counter for http requests.",
+		},
+		[]string{"code", "method"},
+	)
+
+	httpRequestDurationSeconds = promauto.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Namespace: namespace,
 			Subsystem: httpSubsystem,
-			Name:      "request_seconds",
-			Help:      "A histogram of latencies for gitlab-shell http requests",
+			Name:      httpRequestDurationSecondsMetricName,
+			Help:      "A histogram of latencies for http requests.",
 			Buckets: []float64{
-				0.01, /* 10ms */
-				0.05, /* 50ms */
-				0.1,  /* 100ms */
-				0.25, /* 250ms */
-				0.5,  /* 500ms */
-				1.0,  /* 1s */
-				5.0,  /* 5s */
-				10.0, /* 10s */
+				0.005, /* 5ms */
+				0.025, /* 25ms */
+				0.1,   /* 100ms */
+				0.5,   /* 500ms */
+				1.0,   /* 1s */
+				10.0,  /* 10s */
+				30.0,  /* 30s */
+				60.0,  /* 1m */
+				300.0, /* 5m */
 			},
 		},
 		[]string{"code", "method"},
 	)
+
+	httpInFlightRequests = promauto.NewGauge(
+		prometheus.GaugeOpts{
+			Namespace: namespace,
+			Subsystem: httpSubsystem,
+			Name:      httpInFlightRequestsMetricName,
+			Help:      "A gauge of requests currently being performed.",
+		},
+	)
 )
+
+func NewRoundTripper(next http.RoundTripper) promhttp.RoundTripperFunc {
+	rt := next
+
+	rt = promhttp.InstrumentRoundTripperCounter(httpRequestsTotal, rt)
+	rt = promhttp.InstrumentRoundTripperDuration(httpRequestDurationSeconds, rt)
+	return promhttp.InstrumentRoundTripperInFlight(httpInFlightRequests, rt)
+}
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -31,6 +31,9 @@
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
+	metrics.SshdConnectionsInFlight.Inc()
+	defer metrics.SshdConnectionsInFlight.Dec()
+
 	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
 
 	for newChannel := range chans {
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1645483885 -39600
#      Tue Feb 22 09:51:25 2022 +1100
# Node ID 3a505abeb647a2a56ee5a06296654410a688f1db
# Parent  6ea91f81fdce307685c6d4455a6717d3e9f8c3e6
Upgrade golang to 1.17.7

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.6
+golang 1.17.7
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1645501280 0
#      Tue Feb 22 03:41:20 2022 +0000
# Node ID 902a8d109069fb53769ad066378df468847743f3
# Parent  f93b7e6dd81a3ff0cef31d2621913110588e857a
# Parent  3a505abeb647a2a56ee5a06296654410a688f1db
Merge branch 'ashmckenzie/golang-1-17-7' into 'main'

Upgrade golang to 1.17.7

See merge request gitlab-org/gitlab-shell!576

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.6
+golang 1.17.7
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1645579675 -39600
#      Wed Feb 23 12:27:55 2022 +1100
# Node ID e08954f51999499800a68c5b2f6ff72626572d5d
# Parent  902a8d109069fb53769ad066378df468847743f3
Release 13.24.0

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,11 @@
+v13.24.0
+
+- Upgrade golang to 1.17.7 !576
+- Add more metrics for gitlab-sshd !574
+- Move code guidelines to doc/beginners_guide.md !572
+- Add docs for full feature list !571
+- Add aqualls as codeowner for docs files !573
+
 v13.23.2
 
 - Bump labkit version to 1.12.0 !569
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.23.2
+13.24.0
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1645593511 0
#      Wed Feb 23 05:18:31 2022 +0000
# Node ID d8e5a6bac1b1712956515f079a43e701cd5adb32
# Parent  902a8d109069fb53769ad066378df468847743f3
# Parent  e08954f51999499800a68c5b2f6ff72626572d5d
Merge branch 'ashmckenzie/gitlab-shell-v13-24-0' into 'main'

Release 13.24.0

See merge request gitlab-org/gitlab-shell!577

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,11 @@
+v13.24.0
+
+- Upgrade golang to 1.17.7 !576
+- Add more metrics for gitlab-sshd !574
+- Move code guidelines to doc/beginners_guide.md !572
+- Add docs for full feature list !571
+- Add aqualls as codeowner for docs files !573
+
 v13.23.2
 
 - Bump labkit version to 1.12.0 !569
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.23.2
+13.24.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1646562661 -3600
#      Sun Mar 06 11:31:01 2022 +0100
# Branch heptapod
# Node ID 932706903e9732d2d671f456428e81e466d779ce
# Parent  c13639d6d9742fc361e7df2acee2661f37f16cd8
# Parent  7298cb147cee1f199041abc6e9d7649d369e4164
Merged upstream v13.23.2 (GitLab 14.8.2) into heptapod branch

And applied the diff for `.gitlab-ci.yml` directly to `heptapod-ci.yml`

No conflict, no local test failure

diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -1,20 +1,21 @@
-config.yml
-cover.out
-tmp/*
-.idea
 *.log
 *.swp
-/*.log*
-authorized_keys.lock
+.bundle
+.bundle/
 .gitlab_shell_secret
-.bundle
-tags
-.bundle/
-custom_hooks
-hooks/*.d
-/go_build
-/bin/gitlab-sshd
+.idea
+/*.log*
+/bin/check
 /bin/gitlab-shell
 /bin/gitlab-shell-authorized-keys-check
 /bin/gitlab-shell-authorized-principals-check
-/bin/check
+/bin/gitlab-sshd
+/go_build
+authorized_keys.lock
+config.yml
+cover.out
+custom_hooks
+hooks/*.d
+tags
+tmp/*
+vendor
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -37,7 +37,7 @@
     - apt-get update -qq && apt-get install -y ruby ruby-dev
     - ruby -v
     - export PATH=~/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin
-    - gem install --force --bindir /usr/local/bin bundler -v 2.1.4
+    - gem install --force --bindir /usr/local/bin bundler -v 2.3.6
     - bundle install
     # Now set up to run the Golang tests
     - make build
diff --git a/.ruby-version b/.ruby-version
--- a/.ruby-version
+++ b/.ruby-version
@@ -1,1 +1,1 @@
-2.7.4
+2.7.5
diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
-ruby 2.7.4
-golang 1.16.10
+ruby 2.7.5
+golang 1.17.6
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,27 @@
+v13.23.2
+
+- Bump labkit version to 1.12.0 !569
+- Add title and correct copyright notice to license !568
+- Bump go-proxyproto package !563
+- Update Go to version 1.17.6 !562
+
+v13.23.1
+
+- Replace golang.org/x/crypto with gitlab-org/golang-crypto !560
+
+v13.23.0
+
+- Add support for SSHUploadPackWithSidechannel RPC !557
+- Rate limiting documentation !556
+
+v13.22.2
+
+- Update to Ruby 2.7.5 !553
+- Deprecate self_signed_cert config setting !552
+- Send full git request/response in SSHD tests !550
+- Suppress internal errors in client output !549
+- Bump .tool_versions to use Go v1.16.12 !548
+
 v13.22.1
 
 - Remove SSL_CERT_DIR logging !546
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -23,4 +23,4 @@
   rspec (~> 3.8.0)
 
 BUNDLED WITH
-   2.1.4
+   2.3.6
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
-Copyright (c) 2011-2018 GitLab B.V.
+MIT License
 
-With regard to the GitLab Software:
+Copyright (c) 2011-present GitLab B.V.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -9,17 +9,13 @@
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
-For all third party components incorporated into the GitLab Software, those
-components are licensed under the original license provided by the owner of the
-applicable component.
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@
 GO_SOURCES := $(shell find . -name '*.go')
 VERSION_STRING := $(shell ./compute_version || echo unknown)
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
-GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)"
+GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)" -mod=mod
 
 PREFIX ?= /usr/local
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
 1. Call the GitLab Rails API to check if you are authorized, and what Gitaly server your repository is on
 1. Copy data back and forth between the SSH client and the Gitaly server
 
-If you access a GitLab server over HTTP(S) you end up in [gitlab-workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse).
+If you access a GitLab server over HTTP(S) you end up in [gitlab-workhorse](https://gitlab.com/gitlab-org/gitlab/tree/master/workhorse).
 
 An overview of the four cases described above:
 
@@ -98,7 +98,7 @@
 tests requiring Gitaly will be skipped. They will always run in the CI
 environment.
 
-## Git LFS remark
+## Git LFS
 
 Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
 
@@ -117,6 +117,16 @@
 - Logging too much is better than not logging enough. If a message seems too
   verbose, consider reducing the log level before removing the message.
 
+## Rate Limiting
+
+GitLab Shell performs rate-limiting by user account and project for git operations. GitLab Shell accepts git operation requests and then makes a call to the Rails rate-limiter (backed by Redis). If the `user + project` exceeds the rate limit then GitLab Shell will then drop further connection requests for that `user + project`.
+
+The rate-limiter is applied at the git command (plumbing) level. Each command has a rate limit of 600/minute. For example, `git push` has 600/minute and `git pull` has another 600/minute. 
+
+Because they are using the same plumbing command `git-upload-pack`, `git pull` and `git clone` are in effect the same command for the purposes of rate-limiting.
+
+There is also a rate-limiter in place in Gitaly, but the calls will never be made to Gitaly if the rate limit is exceeded in Gitlab Shell (Rails).
+
 ## Releasing
 
 See [PROCESS.md](./PROCESS.md)
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.22.1
+13.23.2
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -162,7 +162,10 @@
 		}
 	}
 	tlsConfig := &tls.Config{
-		RootCAs:            certPool,
+		RootCAs: certPool,
+		// The self_signed_cert config setting is deprecated
+		// The field and its usage is going to be removed in
+		// https://gitlab.com/gitlab-org/gitlab-shell/-/issues/541
 		InsecureSkipVerify: selfSignedCert,
 		MinVersion:         tls.VersionTLS12,
 	}
diff --git a/client/testserver/gitalyserver.go b/client/testserver/gitalyserver.go
--- a/client/testserver/gitalyserver.go
+++ b/client/testserver/gitalyserver.go
@@ -1,6 +1,8 @@
 package testserver
 
 import (
+	"context"
+	"fmt"
 	"net"
 	"os"
 	"path"
@@ -8,12 +10,18 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/labkit/log"
 	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/metadata"
 )
 
-type TestGitalyServer struct{ ReceivedMD metadata.MD }
+type TestGitalyServer struct {
+	ReceivedMD metadata.MD
+	pb.UnimplementedSSHServiceServer
+}
 
 func (s *TestGitalyServer) SSHReceivePack(stream pb.SSHService_SSHReceivePackServer) error {
 	req, err := stream.Recv()
@@ -43,6 +51,26 @@
 	return nil
 }
 
+func (s *TestGitalyServer) SSHUploadPackWithSidechannel(ctx context.Context, req *pb.SSHUploadPackWithSidechannelRequest) (*pb.SSHUploadPackWithSidechannelResponse, error) {
+	conn, err := client.OpenServerSidechannel(ctx)
+	if err != nil {
+		return nil, err
+	}
+	defer conn.Close()
+
+	s.ReceivedMD, _ = metadata.FromIncomingContext(ctx)
+
+	response := []byte("SSHUploadPackWithSidechannel: " + req.Repository.GlRepository)
+	if _, err := fmt.Fprintf(conn, "%04x\x01%s", len(response)+5, response); err != nil {
+		return nil, err
+	}
+	if err := conn.Close(); err != nil {
+		return nil, err
+	}
+
+	return &pb.SSHUploadPackWithSidechannelResponse{}, nil
+}
+
 func (s *TestGitalyServer) SSHUploadArchive(stream pb.SSHService_SSHUploadArchiveServer) error {
 	req, err := stream.Recv()
 	if err != nil {
@@ -67,7 +95,9 @@
 	err := os.MkdirAll(filepath.Dir(gitalySocketPath), 0700)
 	require.NoError(t, err)
 
-	server := grpc.NewServer()
+	server := grpc.NewServer(
+		client.SidechannelServer(log.ContextLogger(context.Background()), insecure.NewCredentials()),
+	)
 
 	listener, err := net.Listen("unix", gitalySocketPath)
 	require.NoError(t, err)
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -5,6 +5,9 @@
 	"os"
 	"reflect"
 
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
+
 	"gitlab.com/gitlab-org/labkit/log"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
@@ -71,7 +74,9 @@
 
 	if err := cmd.Execute(ctx); err != nil {
 		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
-		console.DisplayWarningMessage(err.Error(), readWriter.ErrOut)
+		if grpcstatus.Convert(err).Code() != grpccodes.Internal {
+			console.DisplayWarningMessage(err.Error(), readWriter.ErrOut)
+		}
 		os.Exit(1)
 	}
 
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -408,18 +408,34 @@
 	ensureGitalyRepository(t)
 
 	client := runSSHD(t, successAPI(t))
-
 	session, err := client.NewSession()
 	require.NoError(t, err)
 	defer session.Close()
 
-	output, err := session.Output(fmt.Sprintf("git-upload-pack %s", testRepo))
+	stdin, err := session.StdinPipe()
+	require.NoError(t, err)
+
+	stdout, err := session.StdoutPipe()
+	require.NoError(t, err)
+
+	reader := bufio.NewReader(stdout)
+
+	err = session.Start(fmt.Sprintf("git-upload-pack %s", testRepo))
+	require.NoError(t, err)
+
+	line, err := reader.ReadString('\n')
+	require.NoError(t, err)
+	require.Regexp(t, "^[0-9a-f]{44} HEAD.+", line)
+
+	// Gracefully close connection
+	_, err = fmt.Fprintln(stdin, "0000")
+	require.NoError(t, err)
+
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 
 	outputLines := strings.Split(string(output), "\n")
 
-	require.Regexp(t, "^[0-9a-f]{44} HEAD.+", outputLines[0])
-
 	for i := 1; i < (len(outputLines) - 1); i++ {
 		require.Regexp(t, "^[0-9a-f]{44} refs/(heads|tags)/[^ ]+", outputLines[i])
 	}
@@ -436,11 +452,29 @@
 	require.NoError(t, err)
 	defer session.Close()
 
-	output, err := session.Output(fmt.Sprintf("git-upload-archive %s", testRepo))
+	stdin, err := session.StdinPipe()
+	require.NoError(t, err)
+
+	stdout, err := session.StdoutPipe()
+	require.NoError(t, err)
+
+	reader := bufio.NewReader(stdout)
+
+	err = session.Start(fmt.Sprintf("git-upload-archive %s", testRepo))
 	require.NoError(t, err)
 
-	outputLines := strings.Split(string(output), "\n")
+	_, err = fmt.Fprintln(stdin, "0012argument HEAD\n0000")
+
+	line, err := reader.ReadString('\n')
+	require.Equal(t, "0008ACK\n", line)
+	require.NoError(t, err)
 
-	require.Equal(t, "0008ACK", outputLines[0])
-	require.Regexp(t, "^0000", outputLines[1])
+	// Gracefully close connection
+	_, err = fmt.Fprintln(stdin, "0000")
+	require.NoError(t, err)
+
+	output, err := io.ReadAll(stdout)
+	require.NoError(t, err)
+
+	require.Equal(t, []byte("0000"), output[len(output)-4:])
 }
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -42,6 +42,11 @@
 #  password: somepass
 #  ca_file: /etc/ssl/cert.pem
 #  ca_path: /etc/pki/tls/certs
+#
+#  The self_signed_cert option is deprecated
+#  When it's set to true, any certificate is accepted, which may make machine-in-the-middle attack possible
+#  Certificates specified in ca_file and ca_path are trusted anyway even if they are self-signed
+#  Issue: https://gitlab.com/gitlab-org/gitlab-shell/-/issues/120
   self_signed_cert: false
 
 # File used as authorized_keys for gitlab user
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -8,13 +8,15 @@
 	github.com/mattn/go-shellwords v1.0.11
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
-	github.com/pires/go-proxyproto v0.6.0
-	github.com/prometheus/client_golang v1.10.0
+	github.com/pires/go-proxyproto v0.6.1
+	github.com/prometheus/client_golang v1.11.0
 	github.com/stretchr/testify v1.7.0
-	gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1
-	gitlab.com/gitlab-org/labkit v1.7.0
-	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
+	gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490
+	gitlab.com/gitlab-org/labkit v1.12.0
+	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	google.golang.org/grpc v1.37.0
+	google.golang.org/grpc v1.40.0
 	gopkg.in/yaml.v2 v2.4.0
 )
+
+replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,9 @@
+bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
 bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
 cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
@@ -19,8 +21,13 @@
 cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
-cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
 cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
+cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
+cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
+cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
+cloud.google.com/go v0.92.2 h1:podK44+0gcW5rWGMjJiPH0+rzkCTQx/zT0qF5CLqVkM=
+cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -29,20 +36,58 @@
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo=
+cloud.google.com/go/profiler v0.1.0 h1:MG/rxKC1MztRfEWMGYKFISxyZak5hNh29f0A/z2tvWk=
+cloud.google.com/go/profiler v0.1.0/go.mod h1:D7S7LV/zKbRWkOzYL1b5xytpqt8Ikd/v/yvf1/Tx2pQ=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
 cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/pubsub v1.10.3/go.mod h1:FUcc28GpGxxACoklPsE1sCtbkY4Ix+ro7yvw+h82Jn4=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
-cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.15.0 h1:Ljj+ZXVEhCr/1+4ZhvtteN1ND7UUsNTlduGclLh8GO0=
+cloud.google.com/go/storage v1.15.0/go.mod h1:mjjQMoxxyGH7Jr8K5qrx6N2O0AHsczI61sMNn03GIZI=
+cloud.google.com/go/trace v0.1.0 h1:nUGUK79FOkN0UGUXhBmVBkbu1PYsHe0YyFSPLOD9Npg=
+cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g=
+contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=
+contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
+github.com/Azure/azure-amqp-common-go/v3 v3.1.0/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0=
+github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
+github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go v54.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-service-bus-go v0.10.11/go.mod h1:AWw9eTTWZVZyvgpPahD1ybz3a8/vT3GsJDS8KYex55U=
+github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nUdBD+e64lEuc4sVnuOfNs=
+github.com/Azure/go-amqp v0.13.0/go.mod h1:qj+o8xPCz9tMSbQ83Vp8boHahuRDl5mkNHyt1xlxUTs=
+github.com/Azure/go-amqp v0.13.4/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
+github.com/Azure/go-amqp v0.13.7/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
+github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
+github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
+github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
+github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
+github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE=
+github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
+github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
+github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.7/go.mod h1:AkzUsqkrdmNhfP2i54HqINVQopw0CLDnvHpJ88Zz1eI=
+github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
+github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
+github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
+github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
+github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
+github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
+github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
@@ -52,13 +97,22 @@
 github.com/DataDog/datadog-go v4.4.0+incompatible h1:R7WqXWP4fIOAqWJtUKmSfuc7eDsBT58k9AY5WSHVosk=
 github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
 github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
-github.com/HdrHistogram/hdrhistogram-go v1.1.0 h1:6dpdDPTRoo78HxAJ6T1HfMiKSnqhgRRqzCuPshRkQ7I=
+github.com/DataDog/sketches-go v1.0.0 h1:chm5KSXO7kO+ywGWJ0Zs6tdmWU8PBXSbywFVciL6BG4=
+github.com/DataDog/sketches-go v1.0.0/go.mod h1:O+XkJHWk9w4hDwY2ZUDU31ZC9sNYlYo8DiFsxjYeo1k=
+github.com/GoogleCloudPlatform/cloudsql-proxy v1.22.0/go.mod h1:mAm5O/zik2RFmcpigNjg6nMotDL8ZXJaxKzgGVcSMFA=
 github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
+github.com/HdrHistogram/hdrhistogram-go v1.1.1 h1:cJXY5VLMHgejurPjZH6Fo9rIwRGLefBGdiaENZALqrg=
+github.com/HdrHistogram/hdrhistogram-go v1.1.1/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
 github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
-github.com/Microsoft/go-winio v0.4.19 h1:ZMZG0O5M8bhD0lgCURV8yu3hQ7TGvQ4L1ZW8+J0j9iE=
+github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
+github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
 github.com/Microsoft/go-winio v0.4.19/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
+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/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
@@ -68,24 +122,31 @@
 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
+github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/alexbrainman/sspi v0.0.0-20180125232955-4729b3d4d858/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
 github.com/avast/retry-go v2.4.2+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
+github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
+github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.37.0 h1:GzFnhOIsrGyQ69s7VgqtrG2BG8v7X7vwB3Xpbd/DBBk=
 github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
+github.com/aws/aws-sdk-go v1.38.35 h1:7AlAO0FC+8nFjxiGKEmq0QLpiA8/XFr6eIxgRTwkdTg=
+github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -99,6 +160,9 @@
 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=
 github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
+github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@@ -115,6 +179,8 @@
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
@@ -123,7 +189,10 @@
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
+github.com/coreos/go-systemd/v22 v22.3.1/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@@ -134,9 +203,16 @@
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
 github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
+github.com/dgryski/go-minhash v0.0.0-20170608043002-7fe510aff544/go.mod h1:VBi0XHpFy0xiMySf6YpVbRqrupW4RprJ5QTyN+XvGSM=
+github.com/dgryski/go-spooky v0.0.0-20170606183049-ed3d087f40e2/go.mod h1:hgHYKsoIw7S/hlWtP7wD1wZ7SX1jPTtKko5X9jrOgPQ=
+github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
+github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/dpotapov/go-spnego v0.0.0-20190506202455-c2c609116ad0/go.mod h1:P4f4MSk7h52F2PK0lCapn5+fu47Uf8aRdxDSqgezxZE=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -146,6 +222,8 @@
 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
 github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
+github.com/ekzhu/minhash-lsh v0.0.0-20171225071031-5c06ee8586a1/go.mod h1:yEtCVi+QamvzjEH4U/m6ZGkALIkF2xfQnFp0BcKmIOk=
+github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
 github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -153,47 +231,70 @@
 github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
 github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
+github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
 github.com/getsentry/raven-go v0.1.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/raven-go v0.1.2/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/sentry-go v0.5.1/go.mod h1:B8H7x8TYDPkeWPRzGpIiFO97LZP6rL8A3hEt8lUItMw=
 github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
-github.com/getsentry/sentry-go v0.10.0 h1:6gwY+66NHKqyZrdi6O2jGdo7wGdo9b3B69E01NFgT5g=
 github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
+github.com/getsentry/sentry-go v0.11.0/go.mod h1:KBQIxiZAetw62Cj8Ri964vAEWVdgfaUCn30Q3bCvANo=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
+github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
 github.com/git-lfs/git-lfs v1.5.1-0.20210304194248-2e1d981afbe3/go.mod h1:8Xqs4mqL7o6xEnaXckIgELARTeK7RYtm3pBab7S79Js=
 github.com/git-lfs/gitobj/v2 v2.0.1/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
 github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
 github.com/git-lfs/wildmatch v1.0.4/go.mod h1:SdHAGnApDpnFYQ0vAxbniWR0sn7yLJ3QXo9RRfhn2ew=
+github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
+github.com/go-enry/go-license-detector/v4 v4.3.0/go.mod h1:HaM4wdNxSlz/9Gw0uVOKSQS5JVFqf2Pk8xUPEn6bldI=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
+github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
+github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
+github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
+github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
+github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
 github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
 github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
 github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
 github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
@@ -204,6 +305,8 @@
 github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -228,8 +331,9 @@
 github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
 github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -249,6 +353,7 @@
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -262,13 +367,20 @@
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/go-replayers/grpcreplay v1.0.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
+github.com/google/go-replayers/httpreplay v0.1.2/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -281,14 +393,21 @@
 github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210125172800-10e9aeb4a998/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210804190019-f964ff605595 h1:uNrRgpnKjTfxu4qHaZAAs3eKTYV1EzGF3dAykpnxgDE=
+github.com/google/pprof v0.0.0-20210804190019-f964ff605595/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
+github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
+github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@@ -307,6 +426,7 @@
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
 github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -331,11 +451,14 @@
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 h1:Y4V+SFe7d3iH+9pJCoeWIOS5/xBJIFsltS7E+KJSsJY=
 github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
+github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b/go.mod h1:HmaZGXHdSwQh1jnUlBGN2BeEYOHACLVGzYOXCbsLvxY=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
@@ -345,8 +468,51 @@
 github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
 github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
 github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
+github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
+github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
+github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
+github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
+github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
+github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
+github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
+github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
+github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
+github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
+github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
+github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
+github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
+github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
+github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
+github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
+github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
+github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
+github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
+github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
 github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
 github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jdkato/prose v1.1.0/go.mod h1:jkF0lkxaX5PFSlk9l4Gh9Y+T57TqUZziWT7uZbW5ADg=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
+github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
@@ -360,9 +526,10 @@
 github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jteeuwen/go-bindata v3.0.8-0.20180305030458-6025e8de665b+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
 github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
@@ -380,8 +547,9 @@
 github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
 github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
 github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
-github.com/kelseyhightower/envconfig v1.3.0 h1:IvRS4f2VcIQy6j4ORGIf9145T/AsUB+oY8LyvN8BXNM=
 github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
+github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -389,38 +557,56 @@
 github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
 github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
 github.com/libgit2/git2go/v31 v31.4.12/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec=
+github.com/libgit2/git2go/v32 v32.1.6/go.mod h1:FAA2ePV5PlLjw1ccncFIvu2v8hJSZVN5IzEn4lo/vwo=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7/go.mod h1:Spd59icnvRxSKuyijbbwe5AemzvcyXAUBgApa7VybMw=
 github.com/lightstep/lightstep-tracer-go v0.15.6/go.mod h1:6AMpwZpsyCFwSovxzM78e+AsYxE8sGwiM6C3TytaWeI=
 github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/lightstep/lightstep-tracer-go v0.24.0 h1:qGUbkzHP64NA9r+uIbCvf303IzHPr0M4JlkaDMxXqqk=
 github.com/lightstep/lightstep-tracer-go v0.24.0/go.mod h1:RnONwHKg89zYPmF+Uig5PpHMUcYCFgml8+r4SS53y7A=
+github.com/lightstep/lightstep-tracer-go v0.25.0 h1:sGVnz8h3jTQuHKMbUe2949nXm3Sg09N1UcR3VoQNN5E=
+github.com/lightstep/lightstep-tracer-go v0.25.0/go.mod h1:G1ZAEaqTHFPWpWunnbUn1ADEY/Jvzz7jIOaXwAfD6A8=
 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-shellwords v0.0.0-20190425161501-2444a32a19f4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
@@ -445,10 +631,13 @@
 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/montanaflynn/stats v0.0.0-20151014174947-eeaced052adb/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -461,7 +650,7 @@
 github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
 github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
@@ -504,16 +693,17 @@
 github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
-github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
 github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
-github.com/pires/go-proxyproto v0.6.0 h1:cLJUPnuQdiNf7P/wbeOKmM1khVdaMgTFDLj8h9ZrVYk=
-github.com/pires/go-proxyproto v0.6.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
+github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -528,8 +718,9 @@
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg=
 github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
+github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -541,8 +732,9 @@
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y=
 github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
+github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -552,10 +744,14 @@
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
+github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
+github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
 github.com/rubenv/sql-migrate v0.0.0-20191213152630-06338513c237/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc=
 github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086/go.mod h1:YpdgDXpumPB/+EGmGTYHeiW/0QVFRzBYTNFaxWfPDk4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -563,17 +759,26 @@
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sebest/xff v0.0.0-20160910043805-6c115e0ffa35/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY=
 github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/shirou/gopsutil/v3 v3.21.2 h1:fIOk3hyqV1oGKogfGNjUZa0lUbtlkx3+ZT0IoJth2uM=
+github.com/shirou/gopsutil/v3 v3.21.2/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
+github.com/shogo82148/go-shuffle v0.0.0-20170808115208-59829097ff3b/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@@ -583,6 +788,7 @@
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
@@ -590,18 +796,21 @@
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/ssgelm/cookiejarparser v1.0.1/go.mod h1:DUfC0mpjIzlDN7DzKjXpHj0qMI5m9VrZuz3wSlI+OEI=
 github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -609,11 +818,16 @@
 github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
 github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=
+github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
+github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc=
+github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
 github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-client-go v2.27.0+incompatible h1:6WVONolFJiB8Vx9bq4z9ddyV/SXSpfvvtb7Yl/TGHiE=
 github.com/uber/jaeger-client-go v2.27.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
+github.com/uber/jaeger-client-go v2.29.1+incompatible h1:R9ec3zO3sGpzs0abd43Y+fBZRJ9uiH6lXyR/+u6brW4=
+github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
 github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
 github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
 github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
@@ -629,6 +843,8 @@
 github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
+github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
+github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v0.0.0-20170210233622-6b67b3fab74d/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
@@ -643,19 +859,28 @@
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
 gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
 gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
-gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1 h1:4u44IbgntN1yKgnY/mUabRjHXIchrLPwUwMuDyQ0+ec=
 gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
+gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490 h1:NzcVF6tscgsW890MQfVAhMVnAhXeohPaUGOTuFfIJXs=
+gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490/go.mod h1:taZCYTkDDPh8LCetkdOEx/x+mkvNgGc9GIGlWkXivk0=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
+gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80 h1:rVzTLlEDlX/ocWqNxeuxb9XklLzmI9FdP0teRqkSg00=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
-gitlab.com/gitlab-org/labkit v1.7.0 h1:ufG42cvJ+VkqaUWVEoat/h9PGcW9FFSUPQFz7uwL5wc=
-gitlab.com/gitlab-org/labkit v1.7.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
+gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
+gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
+gitlab.com/gitlab-org/labkit v1.12.0 h1:XVwGqXfHrhEr2bzZbYy/eA7xulAeyvk9HB3Q7nH2H4I=
+gitlab.com/gitlab-org/labkit v1.12.0/go.mod h1:tnvyKiPF/Y0MeBy+ACYWRWexOEQk6PTRGUc9GstcnXc=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
+go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -667,34 +892,25 @@
 go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
+gocloud.dev v0.23.0/go.mod h1:zklCCIIo1N9ELkU2S2E7tW8P8eeMU7oGLeQCXdDwx9Q=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -708,6 +924,7 @@
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -722,8 +939,9 @@
 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=
 golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
@@ -733,8 +951,8 @@
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -748,7 +966,6 @@
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
@@ -758,7 +975,9 @@
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -781,8 +1000,14 @@
 golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
+golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
+golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -795,8 +1020,12 @@
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78 h1:rPRtHfUb0UKZeZ6GH4K4Nt4YRbE9V1u+QZX5upZXqJQ=
 golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a h1:4Kd8OPUx1xgUwrHDaviWZO8MsgoZTZYC3g+8m16RBww=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -819,6 +1048,7 @@
 golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -832,13 +1062,17 @@
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -855,6 +1089,7 @@
 golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -863,14 +1098,31 @@
 golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 h1:ZBu6861dZq7xBnG1bn5SRU0vA8nx42at4+kP07FMTog=
+golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
+golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -879,13 +1131,15 @@
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -900,15 +1154,19 @@
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -947,22 +1205,32 @@
 golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
+gonum.org/v1/gonum v0.7.0/go.mod h1:L02bwd0sqlsvRv41G7wGWFCsVNZFv/k1xzGIxeANHGM=
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
@@ -981,13 +1249,20 @@
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
-google.golang.org/api v0.45.0 h1:pqMffJFLBVUDIoYsHcqtxgQVTsmxMDpYLOc5MT4Jrww=
 google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA=
+google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
+google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
+google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
+google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
+google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
+google.golang.org/api v0.54.0 h1:ECJUVngj71QI6XEm7b1sAf8BljU5inEhMbKPR8Lxhhk=
+google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
@@ -998,6 +1273,7 @@
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
@@ -1018,6 +1294,7 @@
 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@@ -1036,8 +1313,22 @@
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
-google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3 h1:K+7Ig5hjiLVA/i1UFUUbCGimWz5/Ey0lAQjT3QiLaPY=
 google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210420162539-3c870d7478d2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c h1:iLQakcwWG3k/++1q/46apVb1sUQ3IqIdn9yUE6eh/xA=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
 google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -1058,13 +1349,20 @@
 google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
 google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c=
 google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1076,17 +1374,20 @@
 google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/DataDog/dd-trace-go.v1 v1.7.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
-gopkg.in/DataDog/dd-trace-go.v1 v1.30.0 h1:yJJrDYzAlUsDPpAVBjv4VFnXKTbgvaJFTX0646xDPi4=
 gopkg.in/DataDog/dd-trace-go.v1 v1.30.0/go.mod h1:SnKViq44dv/0gjl9RpkP0Y2G3BJSRkp6eYdCSu39iI8=
+gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 h1:DkD0plWEVUB8v/Ru6kRBW30Hy/fRNBC8hPdcExuBZMc=
+gopkg.in/DataDog/dd-trace-go.v1 v1.32.0/go.mod h1:wRKMf/tRASHwH/UOfPQ3IQmVFhTz2/1a1/mpXoIjF54=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
@@ -1095,6 +1396,7 @@
 gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
 gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
 gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
+gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
 gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
 gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
@@ -1102,6 +1404,7 @@
 gopkg.in/jcmturner/gokrb5.v5 v5.3.0/go.mod h1:oQz8Wc5GsctOTgCVyKad1Vw4TCWz5G6gfIQr88RPv4k=
 gopkg.in/jcmturner/rpc.v0 v0.0.2/go.mod h1:NzMq6cRzR9lipgw7WxRBHNx5N8SifBuaCQsOT1kWY/E=
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -1109,6 +1412,7 @@
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1126,6 +1430,8 @@
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
+nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
diff --git a/heptapod-ci.yml b/heptapod-ci.yml
--- a/heptapod-ci.yml
+++ b/heptapod-ci.yml
@@ -23,7 +23,7 @@
     - apt-get update -qq && apt-get install -y ruby ruby-dev
     - ruby -v
     - export PATH=~/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin
-    - gem install --force --bindir /usr/local/bin bundler -v 2.1.4
+    - gem install --force --bindir /usr/local/bin bundler -v 2.3.6
     - bundle install
     # Now set up to run the Golang tests
     - make build
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -30,7 +30,7 @@
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
 		defer cancel()
 
diff --git a/internal/command/twofactorrecover/twofactorrecover.go b/internal/command/twofactorrecover/twofactorrecover.go
--- a/internal/command/twofactorrecover/twofactorrecover.go
+++ b/internal/command/twofactorrecover/twofactorrecover.go
@@ -26,7 +26,7 @@
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.Debug("twofactorrecover: execute: Waiting for user input")
 
-	if c.canContinue() {
+	if c.getUserAnswer(ctx) == "yes" {
 		ctxlog.Debug("twofactorrecover: execute: User chose to continue")
 		c.displayRecoveryCodes(ctx)
 	} else {
@@ -37,16 +37,18 @@
 	return nil
 }
 
-func (c *Command) canContinue() bool {
+func (c *Command) getUserAnswer(ctx context.Context) string {
 	question :=
 		"Are you sure you want to generate new two-factor recovery codes?\n" +
 			"Any existing recovery codes you saved will be invalidated. (yes/no)"
 	fmt.Fprintln(c.ReadWriter.Out, question)
 
 	var answer string
-	fmt.Fscanln(io.LimitReader(c.ReadWriter.In, readerLimit), &answer)
+	if _, err := fmt.Fscanln(io.LimitReader(c.ReadWriter.In, readerLimit), &answer); err != nil {
+		log.ContextLogger(ctx).WithError(err).Debug("twofactorrecover: getUserAnswer: Failed to get user input")
+	}
 
-	return answer == "yes"
+	return answer
 }
 
 func (c *Command) displayRecoveryCodes(ctx context.Context) {
diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -22,7 +22,7 @@
 func (c *Command) Execute(ctx context.Context) error {
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.Info("twofactorverify: execute: waiting for user input")
-	otp := c.getOTP()
+	otp := c.getOTP(ctx)
 
 	ctxlog.Info("twofactorverify: execute: verifying entered OTP")
 	err := c.verifyOTP(ctx, otp)
@@ -35,14 +35,16 @@
 	return nil
 }
 
-func (c *Command) getOTP() string {
+func (c *Command) getOTP(ctx context.Context) string {
 	prompt := "OTP: "
 	fmt.Fprint(c.ReadWriter.Out, prompt)
 
 	var answer string
 	otpLength := int64(64)
 	reader := io.LimitReader(c.ReadWriter.In, otpLength)
-	fmt.Fscanln(reader, &answer)
+	if _, err := fmt.Fscanln(reader, &answer); err != nil {
+		log.ContextLogger(ctx).WithError(err).Debug("twofactorverify: getOTP: Failed to get user input")
+	}
 
 	return answer
 }
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -23,7 +23,7 @@
 
 	request := &pb.SSHUploadArchiveRequest{Repository: &response.Gitaly.Repo}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
 		defer cancel()
 
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -21,13 +21,30 @@
 		Features:    response.Gitaly.Features,
 	}
 
+	if response.Gitaly.UseSidechannel {
+		gc.DialSidechannel = true
+		request := &pb.SSHUploadPackWithSidechannelRequest{
+			Repository:       &response.Gitaly.Repo,
+			GitProtocol:      c.Args.Env.GitProtocolVersion,
+			GitConfigOptions: response.GitConfigOptions,
+		}
+
+		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
+			ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+			defer cancel()
+
+			rw := c.ReadWriter
+			return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
+		})
+	}
+
 	request := &pb.SSHUploadPackRequest{
 		Repository:       &response.Gitaly.Repo,
 		GitProtocol:      c.Args.Env.GitProtocolVersion,
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
 		defer cancel()
 
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -71,3 +71,59 @@
 	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
 	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 }
+
+func TestUploadPack_withSidechannel(t *testing.T) {
+	gitalyAddress, testServer := testserver.StartGitalyServer(t)
+
+	requests := requesthandlers.BuildAllowedWithGitalyHandlersWithSidechannel(t, gitalyAddress)
+	url := testserver.StartHttpServer(t, requests)
+
+	output := &bytes.Buffer{}
+	input := &bytes.Buffer{}
+
+	userId := "1"
+	repo := "group/repo"
+
+	env := sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: "git-upload-pack " + repo,
+		RemoteAddr:      "127.0.0.1",
+	}
+
+	args := &commandargs.Shell{
+		GitlabKeyId: userId,
+		CommandType: commandargs.UploadPack,
+		SshArgs:     []string{"git-upload-pack", repo},
+		Env:         env,
+	}
+
+	cmd := &Command{
+		Config:     &config.Config{GitlabUrl: url},
+		Args:       args,
+		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
+	}
+
+	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
+	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
+
+	err := cmd.Execute(ctx)
+	require.NoError(t, err)
+
+	require.Equal(t, "SSHUploadPackWithSidechannel: "+repo, output.String())
+
+	for k, v := range map[string]string{
+		"gitaly-feature-cache_invalidator":        "true",
+		"gitaly-feature-inforef_uploadpack_cache": "false",
+		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-pack",
+		"key_id":                                  "123",
+		"user_id":                                 "1",
+		"remote_ip":                               "127.0.0.1",
+		"key_type":                                "key",
+	} {
+		actual := testServer.ReceivedMD[k]
+		require.Len(t, actual, 1)
+		require.Equal(t, v, actual[0])
+	}
+	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
+}
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -32,10 +32,11 @@
 }
 
 type Gitaly struct {
-	Repo     pb.Repository     `json:"repository"`
-	Address  string            `json:"address"`
-	Token    string            `json:"token"`
-	Features map[string]string `json:"features"`
+	Repo           pb.Repository     `json:"repository"`
+	Address        string            `json:"address"`
+	Token          string            `json:"token"`
+	Features       map[string]string `json:"features"`
+	UseSidechannel bool              `json:"use_sidechannel"`
 }
 
 type CustomPayloadData struct {
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -11,7 +11,6 @@
 
 	"github.com/stretchr/testify/require"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -54,7 +53,11 @@
 }
 
 func TestSuccessfulResponses(t *testing.T) {
-	client := setup(t, "")
+	okResponse := testResponse{body: responseBody(t, "allowed.json"), status: http.StatusOK}
+	client := setup(t,
+		map[string]testResponse{"first": okResponse},
+		map[string]testResponse{"1": okResponse},
+	)
 
 	testCases := []struct {
 		desc string
@@ -83,8 +86,48 @@
 	}
 }
 
+func TestSidechannelFlag(t *testing.T) {
+	okResponse := testResponse{body: responseBody(t, "allowed_sidechannel.json"), status: http.StatusOK}
+	client := setup(t,
+		map[string]testResponse{"first": okResponse},
+		map[string]testResponse{"1": okResponse},
+	)
+
+	testCases := []struct {
+		desc string
+		args *commandargs.Shell
+		who  string
+	}{
+		{
+			desc: "Provide key id within the request",
+			args: &commandargs.Shell{GitlabKeyId: "1"},
+			who:  "key-1",
+		}, {
+			desc: "Provide username within the request",
+			args: &commandargs.Shell{GitlabUsername: "first"},
+			who:  "user-1",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := client.Verify(context.Background(), tc.args, uploadPackAction, repo)
+			require.NoError(t, err)
+
+			response := buildExpectedResponse(tc.who)
+			response.Gitaly.UseSidechannel = true
+			require.Equal(t, response, result)
+		})
+	}
+}
+
 func TestGeoPushGetCustomAction(t *testing.T) {
-	client := setup(t, "responses/allowed_with_push_payload.json")
+	client := setup(t, map[string]testResponse{
+		"custom": {
+			body:   responseBody(t, "allowed_with_push_payload.json"),
+			status: 300,
+		},
+	}, nil)
 
 	args := &commandargs.Shell{GitlabUsername: "custom"}
 	result, err := client.Verify(context.Background(), args, receivePackAction, repo)
@@ -106,7 +149,12 @@
 }
 
 func TestGeoPullGetCustomAction(t *testing.T) {
-	client := setup(t, "responses/allowed_with_pull_payload.json")
+	client := setup(t, map[string]testResponse{
+		"custom": {
+			body:   responseBody(t, "allowed_with_pull_payload.json"),
+			status: 300,
+		},
+	}, nil)
 
 	args := &commandargs.Shell{GitlabUsername: "custom"}
 	result, err := client.Verify(context.Background(), args, uploadPackAction, repo)
@@ -128,7 +176,11 @@
 }
 
 func TestErrorResponses(t *testing.T) {
-	client := setup(t, "")
+	client := setup(t, nil, map[string]testResponse{
+		"2": {body: []byte(`{"message":"Not allowed!"}`), status: http.StatusForbidden},
+		"3": {body: []byte(`{"message":"broken json!`), status: http.StatusOK},
+		"4": {status: http.StatusForbidden},
+	})
 
 	testCases := []struct {
 		desc          string
@@ -163,20 +215,21 @@
 	}
 }
 
-func setup(t *testing.T, allowedPayload string) *Client {
-	testhelper.PrepareTestRootDir(t)
-
-	body, err := os.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
-	require.NoError(t, err)
+type testResponse struct {
+	body   []byte
+	status int
+}
 
-	var bodyWithPayload []byte
+func responseBody(t *testing.T, name string) []byte {
+	t.Helper()
+	testhelper.PrepareTestRootDir(t)
+	body, err := os.ReadFile(path.Join(testhelper.TestRoot, "responses", name))
+	require.NoError(t, err)
+	return body
+}
 
-	if allowedPayload != "" {
-		allowedWithPayloadPath := path.Join(testhelper.TestRoot, allowedPayload)
-		bodyWithPayload, err = os.ReadFile(allowedWithPayloadPath)
-		require.NoError(t, err)
-	}
-
+func setup(t *testing.T, userResponses, keyResponses map[string]testResponse) *Client {
+	t.Helper()
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/allowed",
@@ -187,36 +240,14 @@
 				var requestBody *Request
 				require.NoError(t, json.Unmarshal(b, &requestBody))
 
-				switch requestBody.Username {
-				case "first":
-					_, err = w.Write(body)
-					require.NoError(t, err)
-				case "second":
-					errBody := map[string]interface{}{
-						"status":  false,
-						"message": "missing user",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(errBody))
-				case "custom":
-					w.WriteHeader(http.StatusMultipleChoices)
-					_, err = w.Write(bodyWithPayload)
+				if tr, ok := userResponses[requestBody.Username]; ok {
+					w.WriteHeader(tr.status)
+					_, err := w.Write(tr.body)
 					require.NoError(t, err)
-				}
-
-				switch requestBody.KeyId {
-				case "1":
-					_, err = w.Write(body)
+				} else if tr, ok := keyResponses[requestBody.KeyId]; ok {
+					w.WriteHeader(tr.status)
+					_, err := w.Write(tr.body)
 					require.NoError(t, err)
-				case "2":
-					w.WriteHeader(http.StatusForbidden)
-					errBody := &client.ErrorResponse{
-						Message: "Not allowed!",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(errBody))
-				case "3":
-					w.Write([]byte("{ \"message\": \"broken json!\""))
-				case "4":
-					w.WriteHeader(http.StatusForbidden)
 				}
 			},
 		},
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -29,21 +29,23 @@
 // GitalyHandlerFunc implementations are responsible for making
 // an appropriate Gitaly call using the provided client and context
 // and returning an error from the Gitaly call.
-type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn) (int32, error)
+type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error)
 
 type GitalyCommand struct {
-	Config      *config.Config
-	ServiceName string
-	Address     string
-	Token       string
-	Features    map[string]string
+	Config          *config.Config
+	ServiceName     string
+	Address         string
+	Token           string
+	Features        map[string]string
+	DialSidechannel bool
 }
 
 // RunGitalyCommand provides a bootstrap for Gitaly commands executed
 // through GitLab-Shell. It ensures that logging, tracing and other
 // common concerns are configured before executing the `handler`.
 func (gc *GitalyCommand) RunGitalyCommand(ctx context.Context, handler GitalyHandlerFunc) error {
-	conn, err := getConn(ctx, gc)
+	registry := client.NewSidechannelRegistry(log.ContextLogger(ctx))
+	conn, err := getConn(ctx, gc, registry)
 	if err != nil {
 		log.ContextLogger(ctx).WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Failed to get connection to execute Git command")
 
@@ -53,7 +55,7 @@
 
 	childCtx := withOutgoingMetadata(ctx, gc.Features)
 	ctxlog := log.ContextLogger(childCtx)
-	exitStatus, err := handler(childCtx, conn)
+	exitStatus, err := handler(childCtx, conn, registry)
 
 	if err != nil {
 		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
@@ -116,7 +118,7 @@
 	return metadata.NewOutgoingContext(ctx, md)
 }
 
-func getConn(ctx context.Context, gc *GitalyCommand) (*grpc.ClientConn, error) {
+func getConn(ctx context.Context, gc *GitalyCommand, registry *client.SidechannelRegistry) (*grpc.ClientConn, error) {
 	if gc.Address == "" {
 		return nil, fmt.Errorf("no gitaly_address given")
 	}
@@ -160,5 +162,9 @@
 		)
 	}
 
+	if gc.DialSidechannel {
+		return client.DialSidechannel(ctx, gc.Address, registry, connOpts)
+	}
+
 	return client.DialContext(ctx, gc.Address, connOpts)
 }
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -11,14 +11,15 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
+	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
-func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn) (int32, error) {
-	return func(ctx context.Context, client *grpc.ClientConn) (int32, error) {
+func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn, *client.SidechannelRegistry) (int32, error) {
+	return func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		require.NotNil(t, ctx)
 		require.NotNil(t, client)
 
@@ -86,7 +87,7 @@
 		t.Run(tt.name, func(t *testing.T) {
 			cmd := tt.gc
 
-			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn) (int32, error) {
+			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn, _ *client.SidechannelRegistry) (int32, error) {
 				md, exists := metadata.FromOutgoingContext(ctx)
 				require.True(t, exists)
 				require.Equal(t, len(tt.want), md.Len())
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -66,8 +66,11 @@
 		default:
 			// Ignore unknown requests but don't terminate the session
 			shouldContinue = true
+
 			if req.WantReply {
-				req.Reply(false, []byte{})
+				if err := req.Reply(false, []byte{}); err != nil {
+					sessionLog.WithError(err).Debug("session: handle: Failed to reply")
+				}
 			}
 		}
 
@@ -100,7 +103,9 @@
 	}
 
 	if req.WantReply {
-		req.Reply(accepted, []byte{})
+		if err := req.Reply(accepted, []byte{}); err != nil {
+			log.ContextLogger(ctx).WithError(err).Debug("session: handleEnv: Failed to reply")
+		}
 	}
 
 	log.WithContextFields(
@@ -124,8 +129,12 @@
 }
 
 func (s *session) handleShell(ctx context.Context, req *ssh.Request) uint32 {
+	ctxlog := log.ContextLogger(ctx)
+
 	if req.WantReply {
-		req.Reply(true, []byte{})
+		if err := req.Reply(true, []byte{}); err != nil {
+			ctxlog.WithError(err).Debug("session: handleShell: Failed to reply")
+		}
 	}
 
 	env := sshenv.Env{
@@ -151,7 +160,6 @@
 	}
 
 	cmdName := reflect.TypeOf(cmd).String()
-	ctxlog := log.ContextLogger(ctx)
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("session: handleShell: executing command")
 
 	if err := cmd.Execute(ctx); err != nil {
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -81,6 +81,14 @@
 }
 
 func BuildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
+	return buildAllowedWithGitalyHandlers(t, gitalyAddress, false)
+}
+
+func BuildAllowedWithGitalyHandlersWithSidechannel(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
+	return buildAllowedWithGitalyHandlers(t, gitalyAddress, true)
+}
+
+func buildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string, useSidechannel bool) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/allowed",
@@ -108,6 +116,9 @@
 						},
 					},
 				}
+				if useSidechannel {
+					body["gitaly"].(map[string]interface{})["use_sidechannel"] = true
+				}
 				require.NoError(t, json.NewEncoder(w).Encode(body))
 			},
 		},
diff --git a/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json b/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
@@ -0,0 +1,23 @@
+{
+	"status": true,
+	"gl_repository": "project-26",
+	"gl_project_path": "group/private",
+	"gl_id": "user-1",
+	"gl_username": "root",
+	"git_config_options": ["option"],
+	"gitaly": {
+		"repository": {
+			"storage_name": "default",
+			"relative_path": "@hashed/5f/9c/5f9c4ab08cac7457e9111a30e4664920607ea2c115a1433d7be98e97e64244ca.git",
+			"git_object_directory": "path/to/git_object_directory",
+			"git_alternate_object_directories": ["path/to/git_alternate_object_directory"],
+			"gl_repository": "project-26",
+			"gl_project_path": "group/private"
+		},
+		"address": "unix:gitaly.socket",
+		"token": "token",
+               "use_sidechannel": true
+	},
+	"git_protocol": "protocol",
+	"gl_console_messages": ["console", "message"]
+}
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1647102567 -3600
#      Sat Mar 12 17:29:27 2022 +0100
# Branch heptapod
# Node ID d93b03881cd6645d9d682ef7c2df356ae3389f8f
# Parent  932706903e9732d2d671f456428e81e466d779ce
HEPTAPOD_CHANGELOG: mentioning GitLab 14.6

diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -11,7 +11,7 @@
 
 ## 13.22.0
 
-- Jump to upstream GitLab Shell v13.22.1 (GitLab 14.5 series)
+- Jump to upstream GitLab Shell v13.22.1 (GitLab 14.5 and 14.6 series)
 
 ## 13.21.0
 
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1647102529 -3600
#      Sat Mar 12 17:28:49 2022 +0100
# Branch heptapod
# Node ID 61814961ec7c871e9a081f4399536e5b8a62fabf
# Parent  d93b03881cd6645d9d682ef7c2df356ae3389f8f
Setting version files for release

diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -9,6 +9,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 13.23.0
+
+- Jump to upstream GitLab Shell v13.23.2 (GitLab 14.8 series)
+
 ## 13.22.0
 
 - Jump to upstream GitLab Shell v13.22.1 (GitLab 14.5 and 14.6 series)
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-13.22.0
+13.23.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1647102623 -3600
#      Sat Mar 12 17:30:23 2022 +0100
# Branch heptapod
# Node ID e7e00e40f1d49d8585386a65c2f83bee9c09dd80
# Parent  61814961ec7c871e9a081f4399536e5b8a62fabf
Added tag heptapod-13.23.0 for changeset 61814961ec7c

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -37,3 +37,4 @@
 f593b81abfe52ca2083440f3f470dce3d13079da heptapod-13.19.2
 b15301c706769652286af112a4437dc8cdfac86e heptapod-13.21.0
 7f259a883e58759e4734cb0f38449379bb36aca6 heptapod-13.22.0
+61814961ec7c871e9a081f4399536e5b8a62fabf heptapod-13.23.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1649193949 -7200
#      Tue Apr 05 23:25:49 2022 +0200
# Branch heptapod-stable
# Node ID bcbcabd3963c27047fe50a5765ba6992a4449bad
# Parent  9f8076c43eab66b8867a86e136f915048237a657
# Parent  e7e00e40f1d49d8585386a65c2f83bee9c09dd80
heptapod#658: making 0.30 the new stable

diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -1,20 +1,21 @@
-config.yml
-cover.out
-tmp/*
-.idea
 *.log
 *.swp
-/*.log*
-authorized_keys.lock
+.bundle
+.bundle/
 .gitlab_shell_secret
-.bundle
-tags
-.bundle/
-custom_hooks
-hooks/*.d
-/go_build
-/bin/gitlab-sshd
+.idea
+/*.log*
+/bin/check
 /bin/gitlab-shell
 /bin/gitlab-shell-authorized-keys-check
 /bin/gitlab-shell-authorized-principals-check
-/bin/check
+/bin/gitlab-sshd
+/go_build
+authorized_keys.lock
+config.yml
+cover.out
+custom_hooks
+hooks/*.d
+tags
+tmp/*
+vendor
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -37,7 +37,7 @@
     - apt-get update -qq && apt-get install -y ruby ruby-dev
     - ruby -v
     - export PATH=~/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin
-    - gem install --force --bindir /usr/local/bin bundler -v 2.1.4
+    - gem install --force --bindir /usr/local/bin bundler -v 2.3.6
     - bundle install
     # Now set up to run the Golang tests
     - make build
diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -37,3 +37,4 @@
 f593b81abfe52ca2083440f3f470dce3d13079da heptapod-13.19.2
 b15301c706769652286af112a4437dc8cdfac86e heptapod-13.21.0
 7f259a883e58759e4734cb0f38449379bb36aca6 heptapod-13.22.0
+61814961ec7c871e9a081f4399536e5b8a62fabf heptapod-13.23.0
diff --git a/.ruby-version b/.ruby-version
--- a/.ruby-version
+++ b/.ruby-version
@@ -1,1 +1,1 @@
-2.7.4
+2.7.5
diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
-ruby 2.7.4
-golang 1.16.12
+ruby 2.7.5
+golang 1.17.6
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,27 @@
+v13.23.2
+
+- Bump labkit version to 1.12.0 !569
+- Add title and correct copyright notice to license !568
+- Bump go-proxyproto package !563
+- Update Go to version 1.17.6 !562
+
+v13.23.1
+
+- Replace golang.org/x/crypto with gitlab-org/golang-crypto !560
+
+v13.23.0
+
+- Add support for SSHUploadPackWithSidechannel RPC !557
+- Rate limiting documentation !556
+
+v13.22.2
+
+- Update to Ruby 2.7.5 !553
+- Deprecate self_signed_cert config setting !552
+- Send full git request/response in SSHD tests !550
+- Suppress internal errors in client output !549
+- Bump .tool_versions to use Go v1.16.12 !548
+
 v13.22.1
 
 - Remove SSL_CERT_DIR logging !546
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -23,4 +23,4 @@
   rspec (~> 3.8.0)
 
 BUNDLED WITH
-   2.1.4
+   2.3.6
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -9,9 +9,13 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 13.23.0
+
+- Jump to upstream GitLab Shell v13.23.2 (GitLab 14.8 series)
+
 ## 13.22.0
 
-- Jump to upstream GitLab Shell v13.22.1 (GitLab 14.5 series)
+- Jump to upstream GitLab Shell v13.22.1 (GitLab 14.5 and 14.6 series)
 
 ## 13.21.0
 
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-13.22.0
+13.23.0
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
-Copyright (c) 2011-2018 GitLab B.V.
+MIT License
 
-With regard to the GitLab Software:
+Copyright (c) 2011-present GitLab B.V.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -9,17 +9,13 @@
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
-For all third party components incorporated into the GitLab Software, those
-components are licensed under the original license provided by the owner of the
-applicable component.
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@
 GO_SOURCES := $(shell find . -name '*.go')
 VERSION_STRING := $(shell ./compute_version || echo unknown)
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
-GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)"
+GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)" -mod=mod
 
 PREFIX ?= /usr/local
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
 1. Call the GitLab Rails API to check if you are authorized, and what Gitaly server your repository is on
 1. Copy data back and forth between the SSH client and the Gitaly server
 
-If you access a GitLab server over HTTP(S) you end up in [gitlab-workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse).
+If you access a GitLab server over HTTP(S) you end up in [gitlab-workhorse](https://gitlab.com/gitlab-org/gitlab/tree/master/workhorse).
 
 An overview of the four cases described above:
 
@@ -98,7 +98,7 @@
 tests requiring Gitaly will be skipped. They will always run in the CI
 environment.
 
-## Git LFS remark
+## Git LFS
 
 Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
 
@@ -117,6 +117,16 @@
 - Logging too much is better than not logging enough. If a message seems too
   verbose, consider reducing the log level before removing the message.
 
+## Rate Limiting
+
+GitLab Shell performs rate-limiting by user account and project for git operations. GitLab Shell accepts git operation requests and then makes a call to the Rails rate-limiter (backed by Redis). If the `user + project` exceeds the rate limit then GitLab Shell will then drop further connection requests for that `user + project`.
+
+The rate-limiter is applied at the git command (plumbing) level. Each command has a rate limit of 600/minute. For example, `git push` has 600/minute and `git pull` has another 600/minute. 
+
+Because they are using the same plumbing command `git-upload-pack`, `git pull` and `git clone` are in effect the same command for the purposes of rate-limiting.
+
+There is also a rate-limiter in place in Gitaly, but the calls will never be made to Gitaly if the rate limit is exceeded in Gitlab Shell (Rails).
+
 ## Releasing
 
 See [PROCESS.md](./PROCESS.md)
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.22.1
+13.23.2
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -162,7 +162,10 @@
 		}
 	}
 	tlsConfig := &tls.Config{
-		RootCAs:            certPool,
+		RootCAs: certPool,
+		// The self_signed_cert config setting is deprecated
+		// The field and its usage is going to be removed in
+		// https://gitlab.com/gitlab-org/gitlab-shell/-/issues/541
 		InsecureSkipVerify: selfSignedCert,
 		MinVersion:         tls.VersionTLS12,
 	}
diff --git a/client/testserver/gitalyserver.go b/client/testserver/gitalyserver.go
--- a/client/testserver/gitalyserver.go
+++ b/client/testserver/gitalyserver.go
@@ -1,6 +1,8 @@
 package testserver
 
 import (
+	"context"
+	"fmt"
 	"net"
 	"os"
 	"path"
@@ -8,12 +10,18 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/labkit/log"
 	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/metadata"
 )
 
-type TestGitalyServer struct{ ReceivedMD metadata.MD }
+type TestGitalyServer struct {
+	ReceivedMD metadata.MD
+	pb.UnimplementedSSHServiceServer
+}
 
 func (s *TestGitalyServer) SSHReceivePack(stream pb.SSHService_SSHReceivePackServer) error {
 	req, err := stream.Recv()
@@ -43,6 +51,26 @@
 	return nil
 }
 
+func (s *TestGitalyServer) SSHUploadPackWithSidechannel(ctx context.Context, req *pb.SSHUploadPackWithSidechannelRequest) (*pb.SSHUploadPackWithSidechannelResponse, error) {
+	conn, err := client.OpenServerSidechannel(ctx)
+	if err != nil {
+		return nil, err
+	}
+	defer conn.Close()
+
+	s.ReceivedMD, _ = metadata.FromIncomingContext(ctx)
+
+	response := []byte("SSHUploadPackWithSidechannel: " + req.Repository.GlRepository)
+	if _, err := fmt.Fprintf(conn, "%04x\x01%s", len(response)+5, response); err != nil {
+		return nil, err
+	}
+	if err := conn.Close(); err != nil {
+		return nil, err
+	}
+
+	return &pb.SSHUploadPackWithSidechannelResponse{}, nil
+}
+
 func (s *TestGitalyServer) SSHUploadArchive(stream pb.SSHService_SSHUploadArchiveServer) error {
 	req, err := stream.Recv()
 	if err != nil {
@@ -67,7 +95,9 @@
 	err := os.MkdirAll(filepath.Dir(gitalySocketPath), 0700)
 	require.NoError(t, err)
 
-	server := grpc.NewServer()
+	server := grpc.NewServer(
+		client.SidechannelServer(log.ContextLogger(context.Background()), insecure.NewCredentials()),
+	)
 
 	listener, err := net.Listen("unix", gitalySocketPath)
 	require.NoError(t, err)
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -5,6 +5,9 @@
 	"os"
 	"reflect"
 
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
+
 	"gitlab.com/gitlab-org/labkit/log"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
@@ -71,7 +74,9 @@
 
 	if err := cmd.Execute(ctx); err != nil {
 		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
-		console.DisplayWarningMessage(err.Error(), readWriter.ErrOut)
+		if grpcstatus.Convert(err).Code() != grpccodes.Internal {
+			console.DisplayWarningMessage(err.Error(), readWriter.ErrOut)
+		}
 		os.Exit(1)
 	}
 
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -408,18 +408,34 @@
 	ensureGitalyRepository(t)
 
 	client := runSSHD(t, successAPI(t))
-
 	session, err := client.NewSession()
 	require.NoError(t, err)
 	defer session.Close()
 
-	output, err := session.Output(fmt.Sprintf("git-upload-pack %s", testRepo))
+	stdin, err := session.StdinPipe()
+	require.NoError(t, err)
+
+	stdout, err := session.StdoutPipe()
+	require.NoError(t, err)
+
+	reader := bufio.NewReader(stdout)
+
+	err = session.Start(fmt.Sprintf("git-upload-pack %s", testRepo))
+	require.NoError(t, err)
+
+	line, err := reader.ReadString('\n')
+	require.NoError(t, err)
+	require.Regexp(t, "^[0-9a-f]{44} HEAD.+", line)
+
+	// Gracefully close connection
+	_, err = fmt.Fprintln(stdin, "0000")
+	require.NoError(t, err)
+
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 
 	outputLines := strings.Split(string(output), "\n")
 
-	require.Regexp(t, "^[0-9a-f]{44} HEAD.+", outputLines[0])
-
 	for i := 1; i < (len(outputLines) - 1); i++ {
 		require.Regexp(t, "^[0-9a-f]{44} refs/(heads|tags)/[^ ]+", outputLines[i])
 	}
@@ -436,11 +452,29 @@
 	require.NoError(t, err)
 	defer session.Close()
 
-	output, err := session.Output(fmt.Sprintf("git-upload-archive %s", testRepo))
+	stdin, err := session.StdinPipe()
+	require.NoError(t, err)
+
+	stdout, err := session.StdoutPipe()
+	require.NoError(t, err)
+
+	reader := bufio.NewReader(stdout)
+
+	err = session.Start(fmt.Sprintf("git-upload-archive %s", testRepo))
 	require.NoError(t, err)
 
-	outputLines := strings.Split(string(output), "\n")
+	_, err = fmt.Fprintln(stdin, "0012argument HEAD\n0000")
+
+	line, err := reader.ReadString('\n')
+	require.Equal(t, "0008ACK\n", line)
+	require.NoError(t, err)
 
-	require.Equal(t, "0008ACK", outputLines[0])
-	require.Regexp(t, "^0000", outputLines[1])
+	// Gracefully close connection
+	_, err = fmt.Fprintln(stdin, "0000")
+	require.NoError(t, err)
+
+	output, err := io.ReadAll(stdout)
+	require.NoError(t, err)
+
+	require.Equal(t, []byte("0000"), output[len(output)-4:])
 }
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -42,6 +42,11 @@
 #  password: somepass
 #  ca_file: /etc/ssl/cert.pem
 #  ca_path: /etc/pki/tls/certs
+#
+#  The self_signed_cert option is deprecated
+#  When it's set to true, any certificate is accepted, which may make machine-in-the-middle attack possible
+#  Certificates specified in ca_file and ca_path are trusted anyway even if they are self-signed
+#  Issue: https://gitlab.com/gitlab-org/gitlab-shell/-/issues/120
   self_signed_cert: false
 
 # File used as authorized_keys for gitlab user
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -8,13 +8,15 @@
 	github.com/mattn/go-shellwords v1.0.11
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
-	github.com/pires/go-proxyproto v0.6.0
-	github.com/prometheus/client_golang v1.10.0
+	github.com/pires/go-proxyproto v0.6.1
+	github.com/prometheus/client_golang v1.11.0
 	github.com/stretchr/testify v1.7.0
-	gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1
-	gitlab.com/gitlab-org/labkit v1.7.0
-	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
+	gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490
+	gitlab.com/gitlab-org/labkit v1.12.0
+	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	google.golang.org/grpc v1.37.0
+	google.golang.org/grpc v1.40.0
 	gopkg.in/yaml.v2 v2.4.0
 )
+
+replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,9 @@
+bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
 bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
 cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
@@ -19,8 +21,13 @@
 cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
-cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
 cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
+cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
+cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
+cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
+cloud.google.com/go v0.92.2 h1:podK44+0gcW5rWGMjJiPH0+rzkCTQx/zT0qF5CLqVkM=
+cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -29,20 +36,58 @@
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo=
+cloud.google.com/go/profiler v0.1.0 h1:MG/rxKC1MztRfEWMGYKFISxyZak5hNh29f0A/z2tvWk=
+cloud.google.com/go/profiler v0.1.0/go.mod h1:D7S7LV/zKbRWkOzYL1b5xytpqt8Ikd/v/yvf1/Tx2pQ=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
 cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/pubsub v1.10.3/go.mod h1:FUcc28GpGxxACoklPsE1sCtbkY4Ix+ro7yvw+h82Jn4=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
-cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.15.0 h1:Ljj+ZXVEhCr/1+4ZhvtteN1ND7UUsNTlduGclLh8GO0=
+cloud.google.com/go/storage v1.15.0/go.mod h1:mjjQMoxxyGH7Jr8K5qrx6N2O0AHsczI61sMNn03GIZI=
+cloud.google.com/go/trace v0.1.0 h1:nUGUK79FOkN0UGUXhBmVBkbu1PYsHe0YyFSPLOD9Npg=
+cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g=
+contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=
+contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
+github.com/Azure/azure-amqp-common-go/v3 v3.1.0/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0=
+github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
+github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go v54.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-service-bus-go v0.10.11/go.mod h1:AWw9eTTWZVZyvgpPahD1ybz3a8/vT3GsJDS8KYex55U=
+github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nUdBD+e64lEuc4sVnuOfNs=
+github.com/Azure/go-amqp v0.13.0/go.mod h1:qj+o8xPCz9tMSbQ83Vp8boHahuRDl5mkNHyt1xlxUTs=
+github.com/Azure/go-amqp v0.13.4/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
+github.com/Azure/go-amqp v0.13.7/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
+github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
+github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
+github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
+github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
+github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE=
+github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
+github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
+github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.7/go.mod h1:AkzUsqkrdmNhfP2i54HqINVQopw0CLDnvHpJ88Zz1eI=
+github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
+github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
+github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
+github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
+github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
+github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
+github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
@@ -52,13 +97,22 @@
 github.com/DataDog/datadog-go v4.4.0+incompatible h1:R7WqXWP4fIOAqWJtUKmSfuc7eDsBT58k9AY5WSHVosk=
 github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
 github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
-github.com/HdrHistogram/hdrhistogram-go v1.1.0 h1:6dpdDPTRoo78HxAJ6T1HfMiKSnqhgRRqzCuPshRkQ7I=
+github.com/DataDog/sketches-go v1.0.0 h1:chm5KSXO7kO+ywGWJ0Zs6tdmWU8PBXSbywFVciL6BG4=
+github.com/DataDog/sketches-go v1.0.0/go.mod h1:O+XkJHWk9w4hDwY2ZUDU31ZC9sNYlYo8DiFsxjYeo1k=
+github.com/GoogleCloudPlatform/cloudsql-proxy v1.22.0/go.mod h1:mAm5O/zik2RFmcpigNjg6nMotDL8ZXJaxKzgGVcSMFA=
 github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
+github.com/HdrHistogram/hdrhistogram-go v1.1.1 h1:cJXY5VLMHgejurPjZH6Fo9rIwRGLefBGdiaENZALqrg=
+github.com/HdrHistogram/hdrhistogram-go v1.1.1/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
 github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
-github.com/Microsoft/go-winio v0.4.19 h1:ZMZG0O5M8bhD0lgCURV8yu3hQ7TGvQ4L1ZW8+J0j9iE=
+github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
+github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
 github.com/Microsoft/go-winio v0.4.19/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
+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/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
@@ -68,24 +122,31 @@
 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
+github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/alexbrainman/sspi v0.0.0-20180125232955-4729b3d4d858/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
 github.com/avast/retry-go v2.4.2+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
+github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
+github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.37.0 h1:GzFnhOIsrGyQ69s7VgqtrG2BG8v7X7vwB3Xpbd/DBBk=
 github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
+github.com/aws/aws-sdk-go v1.38.35 h1:7AlAO0FC+8nFjxiGKEmq0QLpiA8/XFr6eIxgRTwkdTg=
+github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -99,6 +160,9 @@
 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=
 github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
+github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@@ -115,6 +179,8 @@
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
@@ -123,7 +189,10 @@
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
+github.com/coreos/go-systemd/v22 v22.3.1/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@@ -134,9 +203,16 @@
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
 github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
+github.com/dgryski/go-minhash v0.0.0-20170608043002-7fe510aff544/go.mod h1:VBi0XHpFy0xiMySf6YpVbRqrupW4RprJ5QTyN+XvGSM=
+github.com/dgryski/go-spooky v0.0.0-20170606183049-ed3d087f40e2/go.mod h1:hgHYKsoIw7S/hlWtP7wD1wZ7SX1jPTtKko5X9jrOgPQ=
+github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
+github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/dpotapov/go-spnego v0.0.0-20190506202455-c2c609116ad0/go.mod h1:P4f4MSk7h52F2PK0lCapn5+fu47Uf8aRdxDSqgezxZE=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -146,6 +222,8 @@
 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
 github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
+github.com/ekzhu/minhash-lsh v0.0.0-20171225071031-5c06ee8586a1/go.mod h1:yEtCVi+QamvzjEH4U/m6ZGkALIkF2xfQnFp0BcKmIOk=
+github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
 github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -153,47 +231,70 @@
 github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
 github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
+github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
 github.com/getsentry/raven-go v0.1.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/raven-go v0.1.2/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/sentry-go v0.5.1/go.mod h1:B8H7x8TYDPkeWPRzGpIiFO97LZP6rL8A3hEt8lUItMw=
 github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
-github.com/getsentry/sentry-go v0.10.0 h1:6gwY+66NHKqyZrdi6O2jGdo7wGdo9b3B69E01NFgT5g=
 github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
+github.com/getsentry/sentry-go v0.11.0/go.mod h1:KBQIxiZAetw62Cj8Ri964vAEWVdgfaUCn30Q3bCvANo=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
+github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
 github.com/git-lfs/git-lfs v1.5.1-0.20210304194248-2e1d981afbe3/go.mod h1:8Xqs4mqL7o6xEnaXckIgELARTeK7RYtm3pBab7S79Js=
 github.com/git-lfs/gitobj/v2 v2.0.1/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
 github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
 github.com/git-lfs/wildmatch v1.0.4/go.mod h1:SdHAGnApDpnFYQ0vAxbniWR0sn7yLJ3QXo9RRfhn2ew=
+github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
+github.com/go-enry/go-license-detector/v4 v4.3.0/go.mod h1:HaM4wdNxSlz/9Gw0uVOKSQS5JVFqf2Pk8xUPEn6bldI=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
+github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
+github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
+github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
+github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
+github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
 github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
 github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
 github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
 github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
@@ -204,6 +305,8 @@
 github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -228,8 +331,9 @@
 github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
 github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -249,6 +353,7 @@
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -262,13 +367,20 @@
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/go-replayers/grpcreplay v1.0.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
+github.com/google/go-replayers/httpreplay v0.1.2/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -281,14 +393,21 @@
 github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210125172800-10e9aeb4a998/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210804190019-f964ff605595 h1:uNrRgpnKjTfxu4qHaZAAs3eKTYV1EzGF3dAykpnxgDE=
+github.com/google/pprof v0.0.0-20210804190019-f964ff605595/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
+github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
+github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@@ -307,6 +426,7 @@
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
 github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -331,11 +451,14 @@
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 h1:Y4V+SFe7d3iH+9pJCoeWIOS5/xBJIFsltS7E+KJSsJY=
 github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
+github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b/go.mod h1:HmaZGXHdSwQh1jnUlBGN2BeEYOHACLVGzYOXCbsLvxY=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
@@ -345,8 +468,51 @@
 github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
 github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
 github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
+github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
+github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
+github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
+github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
+github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
+github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
+github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
+github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
+github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
+github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
+github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
+github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
+github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
+github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
+github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
+github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
+github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
+github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
+github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
+github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
 github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
 github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jdkato/prose v1.1.0/go.mod h1:jkF0lkxaX5PFSlk9l4Gh9Y+T57TqUZziWT7uZbW5ADg=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
+github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
@@ -360,9 +526,10 @@
 github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jteeuwen/go-bindata v3.0.8-0.20180305030458-6025e8de665b+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
 github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
@@ -380,8 +547,9 @@
 github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
 github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
 github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
-github.com/kelseyhightower/envconfig v1.3.0 h1:IvRS4f2VcIQy6j4ORGIf9145T/AsUB+oY8LyvN8BXNM=
 github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
+github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -389,38 +557,56 @@
 github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
 github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
 github.com/libgit2/git2go/v31 v31.4.12/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec=
+github.com/libgit2/git2go/v32 v32.1.6/go.mod h1:FAA2ePV5PlLjw1ccncFIvu2v8hJSZVN5IzEn4lo/vwo=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7/go.mod h1:Spd59icnvRxSKuyijbbwe5AemzvcyXAUBgApa7VybMw=
 github.com/lightstep/lightstep-tracer-go v0.15.6/go.mod h1:6AMpwZpsyCFwSovxzM78e+AsYxE8sGwiM6C3TytaWeI=
 github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/lightstep/lightstep-tracer-go v0.24.0 h1:qGUbkzHP64NA9r+uIbCvf303IzHPr0M4JlkaDMxXqqk=
 github.com/lightstep/lightstep-tracer-go v0.24.0/go.mod h1:RnONwHKg89zYPmF+Uig5PpHMUcYCFgml8+r4SS53y7A=
+github.com/lightstep/lightstep-tracer-go v0.25.0 h1:sGVnz8h3jTQuHKMbUe2949nXm3Sg09N1UcR3VoQNN5E=
+github.com/lightstep/lightstep-tracer-go v0.25.0/go.mod h1:G1ZAEaqTHFPWpWunnbUn1ADEY/Jvzz7jIOaXwAfD6A8=
 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-shellwords v0.0.0-20190425161501-2444a32a19f4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
@@ -445,10 +631,13 @@
 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/montanaflynn/stats v0.0.0-20151014174947-eeaced052adb/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -461,7 +650,7 @@
 github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
 github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
@@ -504,16 +693,17 @@
 github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
-github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
 github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
-github.com/pires/go-proxyproto v0.6.0 h1:cLJUPnuQdiNf7P/wbeOKmM1khVdaMgTFDLj8h9ZrVYk=
-github.com/pires/go-proxyproto v0.6.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
+github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -528,8 +718,9 @@
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg=
 github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
+github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -541,8 +732,9 @@
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y=
 github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
+github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -552,10 +744,14 @@
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
+github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
+github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
 github.com/rubenv/sql-migrate v0.0.0-20191213152630-06338513c237/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc=
 github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086/go.mod h1:YpdgDXpumPB/+EGmGTYHeiW/0QVFRzBYTNFaxWfPDk4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -563,17 +759,26 @@
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sebest/xff v0.0.0-20160910043805-6c115e0ffa35/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY=
 github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/shirou/gopsutil/v3 v3.21.2 h1:fIOk3hyqV1oGKogfGNjUZa0lUbtlkx3+ZT0IoJth2uM=
+github.com/shirou/gopsutil/v3 v3.21.2/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
+github.com/shogo82148/go-shuffle v0.0.0-20170808115208-59829097ff3b/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@@ -583,6 +788,7 @@
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
@@ -590,18 +796,21 @@
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/ssgelm/cookiejarparser v1.0.1/go.mod h1:DUfC0mpjIzlDN7DzKjXpHj0qMI5m9VrZuz3wSlI+OEI=
 github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -609,11 +818,16 @@
 github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
 github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=
+github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
+github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc=
+github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
 github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-client-go v2.27.0+incompatible h1:6WVONolFJiB8Vx9bq4z9ddyV/SXSpfvvtb7Yl/TGHiE=
 github.com/uber/jaeger-client-go v2.27.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
+github.com/uber/jaeger-client-go v2.29.1+incompatible h1:R9ec3zO3sGpzs0abd43Y+fBZRJ9uiH6lXyR/+u6brW4=
+github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
 github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
 github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
 github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
@@ -629,6 +843,8 @@
 github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
+github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
+github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v0.0.0-20170210233622-6b67b3fab74d/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
@@ -643,19 +859,28 @@
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
 gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
 gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
-gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1 h1:4u44IbgntN1yKgnY/mUabRjHXIchrLPwUwMuDyQ0+ec=
 gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
+gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490 h1:NzcVF6tscgsW890MQfVAhMVnAhXeohPaUGOTuFfIJXs=
+gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490/go.mod h1:taZCYTkDDPh8LCetkdOEx/x+mkvNgGc9GIGlWkXivk0=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
+gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80 h1:rVzTLlEDlX/ocWqNxeuxb9XklLzmI9FdP0teRqkSg00=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
-gitlab.com/gitlab-org/labkit v1.7.0 h1:ufG42cvJ+VkqaUWVEoat/h9PGcW9FFSUPQFz7uwL5wc=
-gitlab.com/gitlab-org/labkit v1.7.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
+gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
+gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
+gitlab.com/gitlab-org/labkit v1.12.0 h1:XVwGqXfHrhEr2bzZbYy/eA7xulAeyvk9HB3Q7nH2H4I=
+gitlab.com/gitlab-org/labkit v1.12.0/go.mod h1:tnvyKiPF/Y0MeBy+ACYWRWexOEQk6PTRGUc9GstcnXc=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
+go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -667,34 +892,25 @@
 go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
+gocloud.dev v0.23.0/go.mod h1:zklCCIIo1N9ELkU2S2E7tW8P8eeMU7oGLeQCXdDwx9Q=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -708,6 +924,7 @@
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -722,8 +939,9 @@
 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=
 golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
@@ -733,8 +951,8 @@
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -748,7 +966,6 @@
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
@@ -758,7 +975,9 @@
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -781,8 +1000,14 @@
 golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
+golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
+golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -795,8 +1020,12 @@
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78 h1:rPRtHfUb0UKZeZ6GH4K4Nt4YRbE9V1u+QZX5upZXqJQ=
 golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a h1:4Kd8OPUx1xgUwrHDaviWZO8MsgoZTZYC3g+8m16RBww=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -819,6 +1048,7 @@
 golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -832,13 +1062,17 @@
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -855,6 +1089,7 @@
 golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -863,14 +1098,31 @@
 golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 h1:ZBu6861dZq7xBnG1bn5SRU0vA8nx42at4+kP07FMTog=
+golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
+golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -879,13 +1131,15 @@
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -900,15 +1154,19 @@
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -947,22 +1205,32 @@
 golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
+gonum.org/v1/gonum v0.7.0/go.mod h1:L02bwd0sqlsvRv41G7wGWFCsVNZFv/k1xzGIxeANHGM=
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
@@ -981,13 +1249,20 @@
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
-google.golang.org/api v0.45.0 h1:pqMffJFLBVUDIoYsHcqtxgQVTsmxMDpYLOc5MT4Jrww=
 google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA=
+google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
+google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
+google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
+google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
+google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
+google.golang.org/api v0.54.0 h1:ECJUVngj71QI6XEm7b1sAf8BljU5inEhMbKPR8Lxhhk=
+google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
@@ -998,6 +1273,7 @@
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
@@ -1018,6 +1294,7 @@
 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@@ -1036,8 +1313,22 @@
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
-google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3 h1:K+7Ig5hjiLVA/i1UFUUbCGimWz5/Ey0lAQjT3QiLaPY=
 google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210420162539-3c870d7478d2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c h1:iLQakcwWG3k/++1q/46apVb1sUQ3IqIdn9yUE6eh/xA=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
 google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -1058,13 +1349,20 @@
 google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
 google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c=
 google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1076,17 +1374,20 @@
 google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/DataDog/dd-trace-go.v1 v1.7.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
-gopkg.in/DataDog/dd-trace-go.v1 v1.30.0 h1:yJJrDYzAlUsDPpAVBjv4VFnXKTbgvaJFTX0646xDPi4=
 gopkg.in/DataDog/dd-trace-go.v1 v1.30.0/go.mod h1:SnKViq44dv/0gjl9RpkP0Y2G3BJSRkp6eYdCSu39iI8=
+gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 h1:DkD0plWEVUB8v/Ru6kRBW30Hy/fRNBC8hPdcExuBZMc=
+gopkg.in/DataDog/dd-trace-go.v1 v1.32.0/go.mod h1:wRKMf/tRASHwH/UOfPQ3IQmVFhTz2/1a1/mpXoIjF54=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
@@ -1095,6 +1396,7 @@
 gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
 gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
 gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
+gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
 gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
 gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
@@ -1102,6 +1404,7 @@
 gopkg.in/jcmturner/gokrb5.v5 v5.3.0/go.mod h1:oQz8Wc5GsctOTgCVyKad1Vw4TCWz5G6gfIQr88RPv4k=
 gopkg.in/jcmturner/rpc.v0 v0.0.2/go.mod h1:NzMq6cRzR9lipgw7WxRBHNx5N8SifBuaCQsOT1kWY/E=
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -1109,6 +1412,7 @@
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1126,6 +1430,8 @@
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
+nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
diff --git a/heptapod-ci.yml b/heptapod-ci.yml
--- a/heptapod-ci.yml
+++ b/heptapod-ci.yml
@@ -23,7 +23,7 @@
     - apt-get update -qq && apt-get install -y ruby ruby-dev
     - ruby -v
     - export PATH=~/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin
-    - gem install --force --bindir /usr/local/bin bundler -v 2.1.4
+    - gem install --force --bindir /usr/local/bin bundler -v 2.3.6
     - bundle install
     # Now set up to run the Golang tests
     - make build
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -30,7 +30,7 @@
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
 		defer cancel()
 
diff --git a/internal/command/twofactorrecover/twofactorrecover.go b/internal/command/twofactorrecover/twofactorrecover.go
--- a/internal/command/twofactorrecover/twofactorrecover.go
+++ b/internal/command/twofactorrecover/twofactorrecover.go
@@ -26,7 +26,7 @@
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.Debug("twofactorrecover: execute: Waiting for user input")
 
-	if c.canContinue() {
+	if c.getUserAnswer(ctx) == "yes" {
 		ctxlog.Debug("twofactorrecover: execute: User chose to continue")
 		c.displayRecoveryCodes(ctx)
 	} else {
@@ -37,16 +37,18 @@
 	return nil
 }
 
-func (c *Command) canContinue() bool {
+func (c *Command) getUserAnswer(ctx context.Context) string {
 	question :=
 		"Are you sure you want to generate new two-factor recovery codes?\n" +
 			"Any existing recovery codes you saved will be invalidated. (yes/no)"
 	fmt.Fprintln(c.ReadWriter.Out, question)
 
 	var answer string
-	fmt.Fscanln(io.LimitReader(c.ReadWriter.In, readerLimit), &answer)
+	if _, err := fmt.Fscanln(io.LimitReader(c.ReadWriter.In, readerLimit), &answer); err != nil {
+		log.ContextLogger(ctx).WithError(err).Debug("twofactorrecover: getUserAnswer: Failed to get user input")
+	}
 
-	return answer == "yes"
+	return answer
 }
 
 func (c *Command) displayRecoveryCodes(ctx context.Context) {
diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -22,7 +22,7 @@
 func (c *Command) Execute(ctx context.Context) error {
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.Info("twofactorverify: execute: waiting for user input")
-	otp := c.getOTP()
+	otp := c.getOTP(ctx)
 
 	ctxlog.Info("twofactorverify: execute: verifying entered OTP")
 	err := c.verifyOTP(ctx, otp)
@@ -35,14 +35,16 @@
 	return nil
 }
 
-func (c *Command) getOTP() string {
+func (c *Command) getOTP(ctx context.Context) string {
 	prompt := "OTP: "
 	fmt.Fprint(c.ReadWriter.Out, prompt)
 
 	var answer string
 	otpLength := int64(64)
 	reader := io.LimitReader(c.ReadWriter.In, otpLength)
-	fmt.Fscanln(reader, &answer)
+	if _, err := fmt.Fscanln(reader, &answer); err != nil {
+		log.ContextLogger(ctx).WithError(err).Debug("twofactorverify: getOTP: Failed to get user input")
+	}
 
 	return answer
 }
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -23,7 +23,7 @@
 
 	request := &pb.SSHUploadArchiveRequest{Repository: &response.Gitaly.Repo}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
 		defer cancel()
 
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -21,13 +21,30 @@
 		Features:    response.Gitaly.Features,
 	}
 
+	if response.Gitaly.UseSidechannel {
+		gc.DialSidechannel = true
+		request := &pb.SSHUploadPackWithSidechannelRequest{
+			Repository:       &response.Gitaly.Repo,
+			GitProtocol:      c.Args.Env.GitProtocolVersion,
+			GitConfigOptions: response.GitConfigOptions,
+		}
+
+		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
+			ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+			defer cancel()
+
+			rw := c.ReadWriter
+			return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
+		})
+	}
+
 	request := &pb.SSHUploadPackRequest{
 		Repository:       &response.Gitaly.Repo,
 		GitProtocol:      c.Args.Env.GitProtocolVersion,
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
 		defer cancel()
 
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -71,3 +71,59 @@
 	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
 	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
 }
+
+func TestUploadPack_withSidechannel(t *testing.T) {
+	gitalyAddress, testServer := testserver.StartGitalyServer(t)
+
+	requests := requesthandlers.BuildAllowedWithGitalyHandlersWithSidechannel(t, gitalyAddress)
+	url := testserver.StartHttpServer(t, requests)
+
+	output := &bytes.Buffer{}
+	input := &bytes.Buffer{}
+
+	userId := "1"
+	repo := "group/repo"
+
+	env := sshenv.Env{
+		IsSSHConnection: true,
+		OriginalCommand: "git-upload-pack " + repo,
+		RemoteAddr:      "127.0.0.1",
+	}
+
+	args := &commandargs.Shell{
+		GitlabKeyId: userId,
+		CommandType: commandargs.UploadPack,
+		SshArgs:     []string{"git-upload-pack", repo},
+		Env:         env,
+	}
+
+	cmd := &Command{
+		Config:     &config.Config{GitlabUrl: url},
+		Args:       args,
+		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
+	}
+
+	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
+	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
+
+	err := cmd.Execute(ctx)
+	require.NoError(t, err)
+
+	require.Equal(t, "SSHUploadPackWithSidechannel: "+repo, output.String())
+
+	for k, v := range map[string]string{
+		"gitaly-feature-cache_invalidator":        "true",
+		"gitaly-feature-inforef_uploadpack_cache": "false",
+		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-pack",
+		"key_id":                                  "123",
+		"user_id":                                 "1",
+		"remote_ip":                               "127.0.0.1",
+		"key_type":                                "key",
+	} {
+		actual := testServer.ReceivedMD[k]
+		require.Len(t, actual, 1)
+		require.Equal(t, v, actual[0])
+	}
+	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
+	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
+}
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -32,10 +32,11 @@
 }
 
 type Gitaly struct {
-	Repo     pb.Repository     `json:"repository"`
-	Address  string            `json:"address"`
-	Token    string            `json:"token"`
-	Features map[string]string `json:"features"`
+	Repo           pb.Repository     `json:"repository"`
+	Address        string            `json:"address"`
+	Token          string            `json:"token"`
+	Features       map[string]string `json:"features"`
+	UseSidechannel bool              `json:"use_sidechannel"`
 }
 
 type CustomPayloadData struct {
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -11,7 +11,6 @@
 
 	"github.com/stretchr/testify/require"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -54,7 +53,11 @@
 }
 
 func TestSuccessfulResponses(t *testing.T) {
-	client := setup(t, "")
+	okResponse := testResponse{body: responseBody(t, "allowed.json"), status: http.StatusOK}
+	client := setup(t,
+		map[string]testResponse{"first": okResponse},
+		map[string]testResponse{"1": okResponse},
+	)
 
 	testCases := []struct {
 		desc string
@@ -83,8 +86,48 @@
 	}
 }
 
+func TestSidechannelFlag(t *testing.T) {
+	okResponse := testResponse{body: responseBody(t, "allowed_sidechannel.json"), status: http.StatusOK}
+	client := setup(t,
+		map[string]testResponse{"first": okResponse},
+		map[string]testResponse{"1": okResponse},
+	)
+
+	testCases := []struct {
+		desc string
+		args *commandargs.Shell
+		who  string
+	}{
+		{
+			desc: "Provide key id within the request",
+			args: &commandargs.Shell{GitlabKeyId: "1"},
+			who:  "key-1",
+		}, {
+			desc: "Provide username within the request",
+			args: &commandargs.Shell{GitlabUsername: "first"},
+			who:  "user-1",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result, err := client.Verify(context.Background(), tc.args, uploadPackAction, repo)
+			require.NoError(t, err)
+
+			response := buildExpectedResponse(tc.who)
+			response.Gitaly.UseSidechannel = true
+			require.Equal(t, response, result)
+		})
+	}
+}
+
 func TestGeoPushGetCustomAction(t *testing.T) {
-	client := setup(t, "responses/allowed_with_push_payload.json")
+	client := setup(t, map[string]testResponse{
+		"custom": {
+			body:   responseBody(t, "allowed_with_push_payload.json"),
+			status: 300,
+		},
+	}, nil)
 
 	args := &commandargs.Shell{GitlabUsername: "custom"}
 	result, err := client.Verify(context.Background(), args, receivePackAction, repo)
@@ -106,7 +149,12 @@
 }
 
 func TestGeoPullGetCustomAction(t *testing.T) {
-	client := setup(t, "responses/allowed_with_pull_payload.json")
+	client := setup(t, map[string]testResponse{
+		"custom": {
+			body:   responseBody(t, "allowed_with_pull_payload.json"),
+			status: 300,
+		},
+	}, nil)
 
 	args := &commandargs.Shell{GitlabUsername: "custom"}
 	result, err := client.Verify(context.Background(), args, uploadPackAction, repo)
@@ -128,7 +176,11 @@
 }
 
 func TestErrorResponses(t *testing.T) {
-	client := setup(t, "")
+	client := setup(t, nil, map[string]testResponse{
+		"2": {body: []byte(`{"message":"Not allowed!"}`), status: http.StatusForbidden},
+		"3": {body: []byte(`{"message":"broken json!`), status: http.StatusOK},
+		"4": {status: http.StatusForbidden},
+	})
 
 	testCases := []struct {
 		desc          string
@@ -163,20 +215,21 @@
 	}
 }
 
-func setup(t *testing.T, allowedPayload string) *Client {
-	testhelper.PrepareTestRootDir(t)
-
-	body, err := os.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
-	require.NoError(t, err)
+type testResponse struct {
+	body   []byte
+	status int
+}
 
-	var bodyWithPayload []byte
+func responseBody(t *testing.T, name string) []byte {
+	t.Helper()
+	testhelper.PrepareTestRootDir(t)
+	body, err := os.ReadFile(path.Join(testhelper.TestRoot, "responses", name))
+	require.NoError(t, err)
+	return body
+}
 
-	if allowedPayload != "" {
-		allowedWithPayloadPath := path.Join(testhelper.TestRoot, allowedPayload)
-		bodyWithPayload, err = os.ReadFile(allowedWithPayloadPath)
-		require.NoError(t, err)
-	}
-
+func setup(t *testing.T, userResponses, keyResponses map[string]testResponse) *Client {
+	t.Helper()
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/allowed",
@@ -187,36 +240,14 @@
 				var requestBody *Request
 				require.NoError(t, json.Unmarshal(b, &requestBody))
 
-				switch requestBody.Username {
-				case "first":
-					_, err = w.Write(body)
-					require.NoError(t, err)
-				case "second":
-					errBody := map[string]interface{}{
-						"status":  false,
-						"message": "missing user",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(errBody))
-				case "custom":
-					w.WriteHeader(http.StatusMultipleChoices)
-					_, err = w.Write(bodyWithPayload)
+				if tr, ok := userResponses[requestBody.Username]; ok {
+					w.WriteHeader(tr.status)
+					_, err := w.Write(tr.body)
 					require.NoError(t, err)
-				}
-
-				switch requestBody.KeyId {
-				case "1":
-					_, err = w.Write(body)
+				} else if tr, ok := keyResponses[requestBody.KeyId]; ok {
+					w.WriteHeader(tr.status)
+					_, err := w.Write(tr.body)
 					require.NoError(t, err)
-				case "2":
-					w.WriteHeader(http.StatusForbidden)
-					errBody := &client.ErrorResponse{
-						Message: "Not allowed!",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(errBody))
-				case "3":
-					w.Write([]byte("{ \"message\": \"broken json!\""))
-				case "4":
-					w.WriteHeader(http.StatusForbidden)
 				}
 			},
 		},
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -29,21 +29,23 @@
 // GitalyHandlerFunc implementations are responsible for making
 // an appropriate Gitaly call using the provided client and context
 // and returning an error from the Gitaly call.
-type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn) (int32, error)
+type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error)
 
 type GitalyCommand struct {
-	Config      *config.Config
-	ServiceName string
-	Address     string
-	Token       string
-	Features    map[string]string
+	Config          *config.Config
+	ServiceName     string
+	Address         string
+	Token           string
+	Features        map[string]string
+	DialSidechannel bool
 }
 
 // RunGitalyCommand provides a bootstrap for Gitaly commands executed
 // through GitLab-Shell. It ensures that logging, tracing and other
 // common concerns are configured before executing the `handler`.
 func (gc *GitalyCommand) RunGitalyCommand(ctx context.Context, handler GitalyHandlerFunc) error {
-	conn, err := getConn(ctx, gc)
+	registry := client.NewSidechannelRegistry(log.ContextLogger(ctx))
+	conn, err := getConn(ctx, gc, registry)
 	if err != nil {
 		log.ContextLogger(ctx).WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Failed to get connection to execute Git command")
 
@@ -53,7 +55,7 @@
 
 	childCtx := withOutgoingMetadata(ctx, gc.Features)
 	ctxlog := log.ContextLogger(childCtx)
-	exitStatus, err := handler(childCtx, conn)
+	exitStatus, err := handler(childCtx, conn, registry)
 
 	if err != nil {
 		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
@@ -116,7 +118,7 @@
 	return metadata.NewOutgoingContext(ctx, md)
 }
 
-func getConn(ctx context.Context, gc *GitalyCommand) (*grpc.ClientConn, error) {
+func getConn(ctx context.Context, gc *GitalyCommand, registry *client.SidechannelRegistry) (*grpc.ClientConn, error) {
 	if gc.Address == "" {
 		return nil, fmt.Errorf("no gitaly_address given")
 	}
@@ -160,5 +162,9 @@
 		)
 	}
 
+	if gc.DialSidechannel {
+		return client.DialSidechannel(ctx, gc.Address, registry, connOpts)
+	}
+
 	return client.DialContext(ctx, gc.Address, connOpts)
 }
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -11,14 +11,15 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
+	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
-func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn) (int32, error) {
-	return func(ctx context.Context, client *grpc.ClientConn) (int32, error) {
+func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn, *client.SidechannelRegistry) (int32, error) {
+	return func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
 		require.NotNil(t, ctx)
 		require.NotNil(t, client)
 
@@ -86,7 +87,7 @@
 		t.Run(tt.name, func(t *testing.T) {
 			cmd := tt.gc
 
-			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn) (int32, error) {
+			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn, _ *client.SidechannelRegistry) (int32, error) {
 				md, exists := metadata.FromOutgoingContext(ctx)
 				require.True(t, exists)
 				require.Equal(t, len(tt.want), md.Len())
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -66,8 +66,11 @@
 		default:
 			// Ignore unknown requests but don't terminate the session
 			shouldContinue = true
+
 			if req.WantReply {
-				req.Reply(false, []byte{})
+				if err := req.Reply(false, []byte{}); err != nil {
+					sessionLog.WithError(err).Debug("session: handle: Failed to reply")
+				}
 			}
 		}
 
@@ -100,7 +103,9 @@
 	}
 
 	if req.WantReply {
-		req.Reply(accepted, []byte{})
+		if err := req.Reply(accepted, []byte{}); err != nil {
+			log.ContextLogger(ctx).WithError(err).Debug("session: handleEnv: Failed to reply")
+		}
 	}
 
 	log.WithContextFields(
@@ -124,8 +129,12 @@
 }
 
 func (s *session) handleShell(ctx context.Context, req *ssh.Request) uint32 {
+	ctxlog := log.ContextLogger(ctx)
+
 	if req.WantReply {
-		req.Reply(true, []byte{})
+		if err := req.Reply(true, []byte{}); err != nil {
+			ctxlog.WithError(err).Debug("session: handleShell: Failed to reply")
+		}
 	}
 
 	env := sshenv.Env{
@@ -151,7 +160,6 @@
 	}
 
 	cmdName := reflect.TypeOf(cmd).String()
-	ctxlog := log.ContextLogger(ctx)
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("session: handleShell: executing command")
 
 	if err := cmd.Execute(ctx); err != nil {
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -81,6 +81,14 @@
 }
 
 func BuildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
+	return buildAllowedWithGitalyHandlers(t, gitalyAddress, false)
+}
+
+func BuildAllowedWithGitalyHandlersWithSidechannel(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
+	return buildAllowedWithGitalyHandlers(t, gitalyAddress, true)
+}
+
+func buildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string, useSidechannel bool) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/allowed",
@@ -108,6 +116,9 @@
 						},
 					},
 				}
+				if useSidechannel {
+					body["gitaly"].(map[string]interface{})["use_sidechannel"] = true
+				}
 				require.NoError(t, json.NewEncoder(w).Encode(body))
 			},
 		},
diff --git a/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json b/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
@@ -0,0 +1,23 @@
+{
+	"status": true,
+	"gl_repository": "project-26",
+	"gl_project_path": "group/private",
+	"gl_id": "user-1",
+	"gl_username": "root",
+	"git_config_options": ["option"],
+	"gitaly": {
+		"repository": {
+			"storage_name": "default",
+			"relative_path": "@hashed/5f/9c/5f9c4ab08cac7457e9111a30e4664920607ea2c115a1433d7be98e97e64244ca.git",
+			"git_object_directory": "path/to/git_object_directory",
+			"git_alternate_object_directories": ["path/to/git_alternate_object_directory"],
+			"gl_repository": "project-26",
+			"gl_project_path": "group/private"
+		},
+		"address": "unix:gitaly.socket",
+		"token": "token",
+               "use_sidechannel": true
+	},
+	"git_protocol": "protocol",
+	"gl_console_messages": ["console", "message"]
+}
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1649262323 -7200
#      Wed Apr 06 18:25:23 2022 +0200
# Branch heptapod
# Node ID 2ffbe5787a8e76b7856e447ca406889930fe0691
# Parent  e7e00e40f1d49d8585386a65c2f83bee9c09dd80
HEPTAPOD_CHANGELOG: more explicit wording

diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -1,6 +1,7 @@
 # Changelog for Heptapod Shell
 
-The CHANGELOG file is about the upstream GitLab Shell.
+See the `CHANGELOG` file for information about upstream GitLab Shell
+versions.
 
 ## Versioning policy
 
# HG changeset patch
# User Sami Hiltunen <shiltunen@gitlab.com>
# Date 1646660844 0
#      Mon Mar 07 13:47:24 2022 +0000
# Node ID 2eafbb7a098ed56d9ef4daae16ea046da3222229
# Parent  d8e5a6bac1b1712956515f079a43e701cd5adb32
Update Gitaly dependency to v14.9.0-rc1

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@
 	github.com/pires/go-proxyproto v0.6.1
 	github.com/prometheus/client_golang v1.11.0
 	github.com/stretchr/testify v1.7.0
-	gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490
+	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc1
 	gitlab.com/gitlab-org/labkit v1.12.0
 	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -149,6 +149,8 @@
 github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
+github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
+github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -583,7 +585,7 @@
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
 github.com/libgit2/git2go/v31 v31.4.12/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec=
-github.com/libgit2/git2go/v32 v32.1.6/go.mod h1:FAA2ePV5PlLjw1ccncFIvu2v8hJSZVN5IzEn4lo/vwo=
+github.com/libgit2/git2go/v33 v33.0.6/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI=
@@ -865,8 +867,8 @@
 gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
 gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
 gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
-gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490 h1:NzcVF6tscgsW890MQfVAhMVnAhXeohPaUGOTuFfIJXs=
-gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490/go.mod h1:taZCYTkDDPh8LCetkdOEx/x+mkvNgGc9GIGlWkXivk0=
+gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc1 h1:9vStRdXxcBQ8dHlVnpV28fwLOgyDkSFIpGnPqwzdTvw=
+gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc1/go.mod h1:Xk5pn6IWsejg3z2X6BRczC5QaI97PRF3GU5OrJ5Amkg=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80 h1:rVzTLlEDlX/ocWqNxeuxb9XklLzmI9FdP0teRqkSg00=
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1646660844 0
#      Mon Mar 07 13:47:24 2022 +0000
# Node ID 76009d797db772436a1e917396dd0dc38769cbe4
# Parent  d8e5a6bac1b1712956515f079a43e701cd5adb32
# Parent  2eafbb7a098ed56d9ef4daae16ea046da3222229
Merge branch 'smh-update-gitaly-vendor' into 'main'

Update Gitaly dependency to v14.9.0-rc1

See merge request gitlab-org/gitlab-shell!578

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@
 	github.com/pires/go-proxyproto v0.6.1
 	github.com/prometheus/client_golang v1.11.0
 	github.com/stretchr/testify v1.7.0
-	gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490
+	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc1
 	gitlab.com/gitlab-org/labkit v1.12.0
 	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -149,6 +149,8 @@
 github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
+github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
+github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -583,7 +585,7 @@
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
 github.com/libgit2/git2go/v31 v31.4.12/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec=
-github.com/libgit2/git2go/v32 v32.1.6/go.mod h1:FAA2ePV5PlLjw1ccncFIvu2v8hJSZVN5IzEn4lo/vwo=
+github.com/libgit2/git2go/v33 v33.0.6/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI=
@@ -865,8 +867,8 @@
 gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
 gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
 gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
-gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490 h1:NzcVF6tscgsW890MQfVAhMVnAhXeohPaUGOTuFfIJXs=
-gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490/go.mod h1:taZCYTkDDPh8LCetkdOEx/x+mkvNgGc9GIGlWkXivk0=
+gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc1 h1:9vStRdXxcBQ8dHlVnpV28fwLOgyDkSFIpGnPqwzdTvw=
+gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc1/go.mod h1:Xk5pn6IWsejg3z2X6BRczC5QaI97PRF3GU5OrJ5Amkg=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80 h1:rVzTLlEDlX/ocWqNxeuxb9XklLzmI9FdP0teRqkSg00=
# HG changeset patch
# User Nick Thomas <nick@gitlab.com>
# Date 1647356020 0
#      Tue Mar 15 14:53:40 2022 +0000
# Node ID 112571ac88ed42af4ae969e73550809db307c6d5
# Parent  76009d797db772436a1e917396dd0dc38769cbe4
Default to info level for an empty log-level

I'd assumed that the `omitempty` directive for LogLevel in
internal/config/config.go would get us this behaviour. If it did, we
wouldn't have had to specify the default twice. Unfortunately, it
doesn't, which is to say that given a config file like:

```
log_level:
```

The default *is* overridden by the empty string.

It's an easy enough fix.

Changelog: fixed

diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -22,6 +22,14 @@
 	return inFmt
 }
 
+func logLevel(inLevel string) string {
+	if inLevel == "" {
+		return "info"
+	}
+
+	return inLevel
+}
+
 func logFile(inFile string) string {
 	if inFile == "" {
 		return "stderr"
@@ -35,7 +43,7 @@
 		log.WithFormatter(logFmt(cfg.LogFormat)),
 		log.WithOutputName(logFile(cfg.LogFile)),
 		log.WithTimezone(time.UTC),
-		log.WithLogLevel(cfg.LogLevel),
+		log.WithLogLevel(logLevel(cfg.LogLevel)),
 	}
 }
 
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -30,9 +30,11 @@
 	tmpFile.Close()
 
 	data, err := os.ReadFile(tmpFile.Name())
+	dataStr := string(data)
 	require.NoError(t, err)
-	require.Contains(t, string(data), `msg":"this is a test"`)
-	require.NotContains(t, string(data), `msg:":"debug log message"`)
+	require.Contains(t, dataStr, `"msg":"this is a test"`)
+	require.NotContains(t, dataStr, `"msg":"debug log message"`)
+	require.NotContains(t, dataStr, `"msg":"unknown log level`)
 }
 
 func TestConfigureWithDebugLogLevel(t *testing.T) {
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1647396566 0
#      Wed Mar 16 02:09:26 2022 +0000
# Node ID 2c4cb630f85ca652c04d738dddc53fc2c2aca5e2
# Parent  76009d797db772436a1e917396dd0dc38769cbe4
# Parent  112571ac88ed42af4ae969e73550809db307c6d5
Merge branch '550-fix-unknown-log-level-message' into 'main'

Default to info level for an empty log-level

Closes #550

See merge request gitlab-org/gitlab-shell!579

diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -22,6 +22,14 @@
 	return inFmt
 }
 
+func logLevel(inLevel string) string {
+	if inLevel == "" {
+		return "info"
+	}
+
+	return inLevel
+}
+
 func logFile(inFile string) string {
 	if inFile == "" {
 		return "stderr"
@@ -35,7 +43,7 @@
 		log.WithFormatter(logFmt(cfg.LogFormat)),
 		log.WithOutputName(logFile(cfg.LogFile)),
 		log.WithTimezone(time.UTC),
-		log.WithLogLevel(cfg.LogLevel),
+		log.WithLogLevel(logLevel(cfg.LogLevel)),
 	}
 }
 
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -30,9 +30,11 @@
 	tmpFile.Close()
 
 	data, err := os.ReadFile(tmpFile.Name())
+	dataStr := string(data)
 	require.NoError(t, err)
-	require.Contains(t, string(data), `msg":"this is a test"`)
-	require.NotContains(t, string(data), `msg:":"debug log message"`)
+	require.Contains(t, dataStr, `"msg":"this is a test"`)
+	require.NotContains(t, dataStr, `"msg":"debug log message"`)
+	require.NotContains(t, dataStr, `"msg":"unknown log level`)
 }
 
 func TestConfigureWithDebugLogLevel(t *testing.T) {
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1645179038 -10800
#      Fri Feb 18 13:10:38 2022 +0300
# Node ID 1682d4cfdc27e9e5d86a6187e4068fa37ca1ddc3
# Parent  76009d797db772436a1e917396dd0dc38769cbe4
Reuse Gitaly conns and Sidechannel

When gitlab-sshd has been introduced we've started running our
own SSH server. In this case we're able to cache and reuse
Gitaly connections and Registry.

It helps to reduce memory usage.

diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -68,6 +68,8 @@
 	ctx, finished := command.Setup(executable.Name, config)
 	defer finished()
 
+	config.GitalyClient.InitSidechannelRegistry(ctx)
+
 	cmdName := reflect.TypeOf(cmd).String()
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("gitlab-shell: main: executing command")
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -69,6 +69,8 @@
 	ctx, finished := command.Setup("gitlab-sshd", cfg)
 	defer finished()
 
+	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+
 	server, err := sshd.NewServer(cfg)
 	if err != nil {
 		log.WithError(err).Fatal("Failed to start GitLab built-in sshd")
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -13,13 +13,7 @@
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
-	gc := &handler.GitalyCommand{
-		Config:      c.Config,
-		ServiceName: string(commandargs.ReceivePack),
-		Address:     response.Gitaly.Address,
-		Token:       response.Gitaly.Token,
-		Features:    response.Gitaly.Features,
-	}
+	gc := handler.NewGitalyCommand(c.Config, string(commandargs.ReceivePack), response)
 
 	request := &pb.SSHReceivePackRequest{
 		Repository:       &response.Gitaly.Repo,
@@ -30,8 +24,8 @@
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
 		rw := c.ReadWriter
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -13,18 +13,12 @@
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
-	gc := &handler.GitalyCommand{
-		Config:      c.Config,
-		ServiceName: string(commandargs.UploadArchive),
-		Address:     response.Gitaly.Address,
-		Token:       response.Gitaly.Token,
-		Features:    response.Gitaly.Features,
-	}
+	gc := handler.NewGitalyCommand(c.Config, string(commandargs.UploadArchive), response)
 
 	request := &pb.SSHUploadArchiveRequest{Repository: &response.Gitaly.Repo}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
 		rw := c.ReadWriter
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -13,26 +13,20 @@
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
-	gc := &handler.GitalyCommand{
-		Config:      c.Config,
-		ServiceName: string(commandargs.UploadPack),
-		Address:     response.Gitaly.Address,
-		Token:       response.Gitaly.Token,
-		Features:    response.Gitaly.Features,
-	}
+	gc := handler.NewGitalyCommand(c.Config, string(commandargs.UploadPack), response)
 
 	if response.Gitaly.UseSidechannel {
-		gc.DialSidechannel = true
 		request := &pb.SSHUploadPackWithSidechannelRequest{
 			Repository:       &response.Gitaly.Repo,
 			GitProtocol:      c.Args.Env.GitProtocolVersion,
 			GitConfigOptions: response.GitConfigOptions,
 		}
 
-		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-			ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+			ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 			defer cancel()
 
+			registry := c.Config.GitalyClient.SidechannelRegistry
 			rw := c.ReadWriter
 			return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
 		})
@@ -44,8 +38,8 @@
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
 		rw := c.ReadWriter
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -97,15 +97,18 @@
 		Env:         env,
 	}
 
+	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
+	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
+
+	cfg := &config.Config{GitlabUrl: url}
+	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+
 	cmd := &Command{
-		Config:     &config.Config{GitlabUrl: url},
+		Config:     cfg,
 		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
 
-	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
-	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
-
 	err := cmd.Execute(ctx)
 	require.NoError(t, err)
 
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -12,6 +12,7 @@
 	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
 
@@ -59,6 +60,8 @@
 	httpClient     *client.HttpClient
 	httpClientErr  error
 	httpClientOnce sync.Once
+
+	GitalyClient gitaly.Client
 }
 
 // The defaults to apply before parsing the config file(s).
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
new file mode 100644
--- /dev/null
+++ b/internal/gitaly/gitaly.go
@@ -0,0 +1,133 @@
+package gitaly
+
+import (
+	"context"
+	"fmt"
+	"sync"
+
+	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
+	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
+	"google.golang.org/grpc"
+
+	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
+	"gitlab.com/gitlab-org/gitaly/v14/client"
+	gitalyclient "gitlab.com/gitlab-org/gitaly/v14/client"
+	"gitlab.com/gitlab-org/labkit/correlation"
+	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
+	"gitlab.com/gitlab-org/labkit/log"
+	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+)
+
+type Command struct {
+	ServiceName     string
+	Address         string
+	Token           string
+	DialSidechannel bool
+}
+
+type connectionsCache struct {
+	sync.RWMutex
+
+	connections map[Command]*grpc.ClientConn
+}
+
+type Client struct {
+	SidechannelRegistry *gitalyclient.SidechannelRegistry
+
+	cache connectionsCache
+}
+
+func (c *Client) InitSidechannelRegistry(ctx context.Context) {
+	c.SidechannelRegistry = gitalyclient.NewSidechannelRegistry(log.ContextLogger(ctx))
+}
+
+func (c *Client) GetConnection(ctx context.Context, cmd Command) (*grpc.ClientConn, error) {
+	c.cache.RLock()
+	conn := c.cache.connections[cmd]
+	c.cache.RUnlock()
+
+	if conn != nil {
+		return conn, nil
+	}
+
+	c.cache.Lock()
+	defer c.cache.Unlock()
+
+	if conn := c.cache.connections[cmd]; conn != nil {
+		return conn, nil
+	}
+
+	conn, err := c.newConnection(ctx, cmd)
+	if err != nil {
+		return nil, err
+	}
+
+	if c.cache.connections == nil {
+		c.cache.connections = make(map[Command]*grpc.ClientConn)
+	}
+
+	c.cache.connections[cmd] = conn
+
+	return conn, nil
+}
+
+func (c *Client) newConnection(ctx context.Context, cmd Command) (conn *grpc.ClientConn, err error) {
+	defer func() {
+		label := "ok"
+		if err != nil {
+			label = "fail"
+		}
+		metrics.GitalyConnectionsTotal.WithLabelValues(label).Inc()
+	}()
+
+	if cmd.Address == "" {
+		return nil, fmt.Errorf("no gitaly_address given")
+	}
+
+	serviceName := correlation.ExtractClientNameFromContext(ctx)
+	if serviceName == "" {
+		serviceName = "gitlab-shell-unknown"
+
+		log.WithContextFields(ctx, log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
+	}
+
+	serviceName = fmt.Sprintf("%s-%s", serviceName, cmd.ServiceName)
+
+	connOpts := client.DefaultDialOpts
+	connOpts = append(
+		connOpts,
+		grpc.WithStreamInterceptor(
+			grpc_middleware.ChainStreamClient(
+				grpctracing.StreamClientTracingInterceptor(),
+				grpc_prometheus.StreamClientInterceptor,
+				grpccorrelation.StreamClientCorrelationInterceptor(
+					grpccorrelation.WithClientName(serviceName),
+				),
+			),
+		),
+
+		grpc.WithUnaryInterceptor(
+			grpc_middleware.ChainUnaryClient(
+				grpctracing.UnaryClientTracingInterceptor(),
+				grpc_prometheus.UnaryClientInterceptor,
+				grpccorrelation.UnaryClientCorrelationInterceptor(
+					grpccorrelation.WithClientName(serviceName),
+				),
+			),
+		),
+	)
+
+	if cmd.Token != "" {
+		connOpts = append(connOpts,
+			grpc.WithPerRPCCredentials(gitalyauth.RPCCredentialsV2(cmd.Token)),
+		)
+	}
+
+	if cmd.DialSidechannel {
+		return client.DialSidechannel(ctx, cmd.Address, c.SidechannelRegistry, connOpts)
+	}
+
+	return client.DialContext(ctx, cmd.Address, connOpts)
+}
diff --git a/internal/gitaly/gitaly_test.go b/internal/gitaly/gitaly_test.go
new file mode 100644
--- /dev/null
+++ b/internal/gitaly/gitaly_test.go
@@ -0,0 +1,53 @@
+package gitaly
+
+import (
+	"context"
+	"testing"
+
+	"github.com/prometheus/client_golang/prometheus/testutil"
+	"github.com/stretchr/testify/require"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+)
+
+func TestPrometheusMetrics(t *testing.T) {
+	metrics.GitalyConnectionsTotal.Reset()
+
+	c := &Client{}
+
+	cmd := Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9999"}
+	c.newConnection(context.Background(), cmd)
+	c.newConnection(context.Background(), cmd)
+
+	require.Equal(t, 1, testutil.CollectAndCount(metrics.GitalyConnectionsTotal))
+	require.InDelta(t, 2, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("ok")), 0.1)
+	require.InDelta(t, 0, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("fail")), 0.1)
+
+	cmd = Command{Address: ""}
+	c.newConnection(context.Background(), cmd)
+
+	require.InDelta(t, 2, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("ok")), 0.1)
+	require.InDelta(t, 1, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("fail")), 0.1)
+}
+
+func TestCachedConnections(t *testing.T) {
+	c := &Client{}
+
+	require.Len(t, c.cache.connections, 0)
+
+	cmd := Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9999"}
+
+	conn, err := c.GetConnection(context.Background(), cmd)
+	require.NoError(t, err)
+	require.Len(t, c.cache.connections, 1)
+
+	newConn, err := c.GetConnection(context.Background(), cmd)
+	require.NoError(t, err)
+	require.Len(t, c.cache.connections, 1)
+	require.Equal(t, conn, newConn)
+
+	cmd = Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9998"}
+	_, err = c.GetConnection(context.Background(), cmd)
+	require.NoError(t, err)
+	require.Len(t, c.cache.connections, 2)
+}
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -6,56 +6,57 @@
 	"strconv"
 	"strings"
 
-	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
-	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
 	"google.golang.org/grpc"
 	grpccodes "google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 
-	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
-	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/labkit/correlation"
-	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
 	"gitlab.com/gitlab-org/labkit/log"
-	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
 )
 
 // GitalyHandlerFunc implementations are responsible for making
 // an appropriate Gitaly call using the provided client and context
 // and returning an error from the Gitaly call.
-type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error)
+type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn) (int32, error)
 
 type GitalyCommand struct {
-	Config          *config.Config
-	ServiceName     string
-	Address         string
-	Token           string
-	Features        map[string]string
-	DialSidechannel bool
+	Config   *config.Config
+	Response *accessverifier.Response
+	Command  gitaly.Command
+}
+
+func NewGitalyCommand(cfg *config.Config, serviceName string, response *accessverifier.Response) *GitalyCommand {
+	gc := gitaly.Command{
+		ServiceName:     serviceName,
+		Address:         response.Gitaly.Address,
+		Token:           response.Gitaly.Token,
+		DialSidechannel: response.Gitaly.UseSidechannel,
+	}
+
+	return &GitalyCommand{Config: cfg, Response: response, Command: gc}
 }
 
 // RunGitalyCommand provides a bootstrap for Gitaly commands executed
 // through GitLab-Shell. It ensures that logging, tracing and other
 // common concerns are configured before executing the `handler`.
 func (gc *GitalyCommand) RunGitalyCommand(ctx context.Context, handler GitalyHandlerFunc) error {
-	registry := client.NewSidechannelRegistry(log.ContextLogger(ctx))
-	conn, err := getConn(ctx, gc, registry)
+	// We leave the connection open for future reuse
+	conn, err := gc.getConn(ctx)
 	if err != nil {
 		log.ContextLogger(ctx).WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Failed to get connection to execute Git command")
 
 		return err
 	}
-	defer conn.Close()
 
-	childCtx := withOutgoingMetadata(ctx, gc.Features)
+	childCtx := withOutgoingMetadata(ctx, gc.Response.Gitaly.Features)
 	ctxlog := log.ContextLogger(childCtx)
-	exitStatus, err := handler(childCtx, conn, registry)
+	exitStatus, err := handler(childCtx, conn)
 
 	if err != nil {
 		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
@@ -72,35 +73,35 @@
 
 // PrepareContext wraps a given context with a correlation ID and logs the command to
 // be run.
-func (gc *GitalyCommand) PrepareContext(ctx context.Context, repository *pb.Repository, response *accessverifier.Response, env sshenv.Env) (context.Context, context.CancelFunc) {
+func (gc *GitalyCommand) PrepareContext(ctx context.Context, repository *pb.Repository, env sshenv.Env) (context.Context, context.CancelFunc) {
 	ctx, cancel := context.WithCancel(ctx)
-	gc.LogExecution(ctx, repository, response, env)
+	gc.LogExecution(ctx, repository, env)
 
 	md, ok := metadata.FromOutgoingContext(ctx)
 	if !ok {
 		md = metadata.New(nil)
 	}
-	md.Append("key_id", strconv.Itoa(response.KeyId))
-	md.Append("key_type", response.KeyType)
-	md.Append("user_id", response.UserId)
-	md.Append("username", response.Username)
+	md.Append("key_id", strconv.Itoa(gc.Response.KeyId))
+	md.Append("key_type", gc.Response.KeyType)
+	md.Append("user_id", gc.Response.UserId)
+	md.Append("username", gc.Response.Username)
 	md.Append("remote_ip", env.RemoteAddr)
 	ctx = metadata.NewOutgoingContext(ctx, md)
 
 	return ctx, cancel
 }
 
-func (gc *GitalyCommand) LogExecution(ctx context.Context, repository *pb.Repository, response *accessverifier.Response, env sshenv.Env) {
+func (gc *GitalyCommand) LogExecution(ctx context.Context, repository *pb.Repository, env sshenv.Env) {
 	fields := log.Fields{
-		"command":         gc.ServiceName,
+		"command":         gc.Command.ServiceName,
 		"gl_project_path": repository.GlProjectPath,
 		"gl_repository":   repository.GlRepository,
-		"user_id":         response.UserId,
-		"username":        response.Username,
+		"user_id":         gc.Response.UserId,
+		"username":        gc.Response.Username,
 		"git_protocol":    env.GitProtocolVersion,
 		"remote_ip":       env.RemoteAddr,
-		"gl_key_type":     response.KeyType,
-		"gl_key_id":       response.KeyId,
+		"gl_key_type":     gc.Response.KeyType,
+		"gl_key_id":       gc.Response.KeyId,
 	}
 
 	log.WithContextFields(ctx, fields).Info("executing git command")
@@ -118,53 +119,6 @@
 	return metadata.NewOutgoingContext(ctx, md)
 }
 
-func getConn(ctx context.Context, gc *GitalyCommand, registry *client.SidechannelRegistry) (*grpc.ClientConn, error) {
-	if gc.Address == "" {
-		return nil, fmt.Errorf("no gitaly_address given")
-	}
-
-	serviceName := correlation.ExtractClientNameFromContext(ctx)
-	if serviceName == "" {
-		serviceName = "gitlab-shell-unknown"
-
-		log.WithContextFields(ctx, log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
-	}
-
-	serviceName = fmt.Sprintf("%s-%s", serviceName, gc.ServiceName)
-
-	connOpts := client.DefaultDialOpts
-	connOpts = append(
-		connOpts,
-		grpc.WithStreamInterceptor(
-			grpc_middleware.ChainStreamClient(
-				grpctracing.StreamClientTracingInterceptor(),
-				grpc_prometheus.StreamClientInterceptor,
-				grpccorrelation.StreamClientCorrelationInterceptor(
-					grpccorrelation.WithClientName(serviceName),
-				),
-			),
-		),
-
-		grpc.WithUnaryInterceptor(
-			grpc_middleware.ChainUnaryClient(
-				grpctracing.UnaryClientTracingInterceptor(),
-				grpc_prometheus.UnaryClientInterceptor,
-				grpccorrelation.UnaryClientCorrelationInterceptor(
-					grpccorrelation.WithClientName(serviceName),
-				),
-			),
-		),
-	)
-
-	if gc.Token != "" {
-		connOpts = append(connOpts,
-			grpc.WithPerRPCCredentials(gitalyauth.RPCCredentialsV2(gc.Token)),
-		)
-	}
-
-	if gc.DialSidechannel {
-		return client.DialSidechannel(ctx, gc.Address, registry, connOpts)
-	}
-
-	return client.DialContext(ctx, gc.Address, connOpts)
+func (gc *GitalyCommand) getConn(ctx context.Context) (*grpc.ClientConn, error) {
+	return gc.Config.GitalyClient.GetConnection(ctx, gc.Command)
 }
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -11,15 +11,15 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
-func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn, *client.SidechannelRegistry) (int32, error) {
-	return func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
+func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn) (int32, error) {
+	return func(ctx context.Context, client *grpc.ClientConn) (int32, error) {
 		require.NotNil(t, ctx)
 		require.NotNil(t, client)
 
@@ -28,10 +28,13 @@
 }
 
 func TestRunGitalyCommand(t *testing.T) {
-	cmd := GitalyCommand{
-		Config:  &config.Config{},
-		Address: "tcp://localhost:9999",
-	}
+	cmd := NewGitalyCommand(
+		&config.Config{},
+		string(commandargs.UploadPack),
+		&accessverifier.Response{
+			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
+		},
+	)
 
 	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, nil))
 	require.NoError(t, err)
@@ -41,6 +44,32 @@
 	require.Equal(t, err, expectedErr)
 }
 
+func TestCachingOfGitalyConnections(t *testing.T) {
+	ctx := context.Background()
+	cfg := &config.Config{}
+	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+	response := &accessverifier.Response{
+		Username: "user",
+		Gitaly: accessverifier.Gitaly{
+			Address:        "tcp://localhost:9999",
+			Token:          "token",
+			UseSidechannel: true,
+		},
+	}
+
+	cmd := NewGitalyCommand(cfg, string(commandargs.UploadPack), response)
+
+	conn, err := cmd.getConn(ctx)
+	require.NoError(t, err)
+
+	// Reuses connection for different users
+	response.Username = "another-user"
+	cmd = NewGitalyCommand(cfg, string(commandargs.UploadPack), response)
+	newConn, err := cmd.getConn(ctx)
+	require.NoError(t, err)
+	require.Equal(t, conn, newConn)
+}
+
 func TestMissingGitalyAddress(t *testing.T) {
 	cmd := GitalyCommand{Config: &config.Config{}}
 
@@ -49,10 +78,13 @@
 }
 
 func TestUnavailableGitalyErr(t *testing.T) {
-	cmd := GitalyCommand{
-		Config:  &config.Config{},
-		Address: "tcp://localhost:9999",
-	}
+	cmd := NewGitalyCommand(
+		&config.Config{},
+		string(commandargs.UploadPack),
+		&accessverifier.Response{
+			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
+		},
+	)
 
 	expectedErr := grpcstatus.Error(grpccodes.Unavailable, "error")
 	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, expectedErr))
@@ -68,15 +100,20 @@
 	}{
 		{
 			name: "gitaly_feature_flags",
-			gc: &GitalyCommand{
-				Config:  &config.Config{},
-				Address: "tcp://localhost:9999",
-				Features: map[string]string{
-					"gitaly-feature-cache_invalidator":        "true",
-					"other-ff":                                "true",
-					"gitaly-feature-inforef_uploadpack_cache": "false",
+			gc: NewGitalyCommand(
+				&config.Config{},
+				string(commandargs.UploadPack),
+				&accessverifier.Response{
+					Gitaly: accessverifier.Gitaly{
+						Address: "tcp://localhost:9999",
+						Features: map[string]string{
+							"gitaly-feature-cache_invalidator":        "true",
+							"other-ff":                                "true",
+							"gitaly-feature-inforef_uploadpack_cache": "false",
+						},
+					},
 				},
-			},
+			),
 			want: map[string]string{
 				"gitaly-feature-cache_invalidator":        "true",
 				"gitaly-feature-inforef_uploadpack_cache": "false",
@@ -87,7 +124,7 @@
 		t.Run(tt.name, func(t *testing.T) {
 			cmd := tt.gc
 
-			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn, _ *client.SidechannelRegistry) (int32, error) {
+			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn) (int32, error) {
 				md, exists := metadata.FromOutgoingContext(ctx)
 				require.True(t, exists)
 				require.Equal(t, len(tt.want), md.Len())
@@ -117,10 +154,19 @@
 	}{
 		{
 			name: "client_identity",
-			gc: &GitalyCommand{
-				Config:  &config.Config{},
-				Address: "tcp://localhost:9999",
-			},
+			gc: NewGitalyCommand(
+				&config.Config{},
+				string(commandargs.UploadPack),
+				&accessverifier.Response{
+					KeyId:    1,
+					KeyType:  "key",
+					UserId:   "6",
+					Username: "jane.doe",
+					Gitaly: accessverifier.Gitaly{
+						Address: "tcp://localhost:9999",
+					},
+				},
+			),
 			env: sshenv.Env{
 				GitProtocolVersion: "protocol",
 				IsSSHConnection:    true,
@@ -134,12 +180,6 @@
 				GlRepository:                  "project-26",
 				GlProjectPath:                 "group/private",
 			},
-			response: &accessverifier.Response{
-				KeyId:    1,
-				KeyType:  "key",
-				UserId:   "6",
-				Username: "jane.doe",
-			},
 			want: map[string]string{
 				"key_id":    "1",
 				"key_type":  "key",
@@ -153,7 +193,7 @@
 		t.Run(tt.name, func(t *testing.T) {
 			ctx := context.Background()
 
-			ctx, cancel := tt.gc.PrepareContext(ctx, tt.repo, tt.response, tt.env)
+			ctx, cancel := tt.gc.PrepareContext(ctx, tt.repo, tt.env)
 			defer cancel()
 
 			md, exists := metadata.FromOutgoingContext(ctx)
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -9,9 +9,10 @@
 )
 
 const (
-	namespace     = "gitlab_shell"
-	sshdSubsystem = "sshd"
-	httpSubsystem = "http"
+	namespace       = "gitlab_shell"
+	sshdSubsystem   = "sshd"
+	httpSubsystem   = "http"
+	gitalySubsystem = "gitaly"
 
 	httpInFlightRequestsMetricName       = "in_flight_requests"
 	httpRequestsTotalMetricName          = "requests_total"
@@ -20,6 +21,8 @@
 	sshdConnectionsInFlightName = "in_flight_connections"
 	sshdConnectionDuration      = "connection_duration_seconds"
 	sshdHitMaxSessions          = "concurrent_limited_sessions_total"
+
+	gitalyConnectionsTotalName = "connections_total"
 )
 
 var (
@@ -61,6 +64,16 @@
 		},
 	)
 
+	GitalyConnectionsTotal = promauto.NewCounterVec(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: gitalySubsystem,
+			Name:      gitalyConnectionsTotalName,
+			Help:      "Number of Gitaly connections that have been established",
+		},
+		[]string{"status"},
+	)
+
 	// The metrics and the buckets size are similar to the ones we have for handlers in Labkit
 	// When the MR: https://gitlab.com/gitlab-org/labkit/-/merge_requests/150 is merged,
 	// these metrics can be refactored out of Gitlab Shell code by using the helper function from Labkit
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1647614831 0
#      Fri Mar 18 14:47:11 2022 +0000
# Node ID 80642f2839758d20b0e5a87fb36460e1a9e6e0bf
# Parent  2c4cb630f85ca652c04d738dddc53fc2c2aca5e2
# Parent  1682d4cfdc27e9e5d86a6187e4068fa37ca1ddc3
Merge branch 'id-reuse-grpc-connections-and-sidechannel' into 'main'

Reuse Gitaly connections and sidechannel

See merge request gitlab-org/gitlab-shell!575

diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -68,6 +68,8 @@
 	ctx, finished := command.Setup(executable.Name, config)
 	defer finished()
 
+	config.GitalyClient.InitSidechannelRegistry(ctx)
+
 	cmdName := reflect.TypeOf(cmd).String()
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("gitlab-shell: main: executing command")
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -69,6 +69,8 @@
 	ctx, finished := command.Setup("gitlab-sshd", cfg)
 	defer finished()
 
+	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+
 	server, err := sshd.NewServer(cfg)
 	if err != nil {
 		log.WithError(err).Fatal("Failed to start GitLab built-in sshd")
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -13,13 +13,7 @@
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
-	gc := &handler.GitalyCommand{
-		Config:      c.Config,
-		ServiceName: string(commandargs.ReceivePack),
-		Address:     response.Gitaly.Address,
-		Token:       response.Gitaly.Token,
-		Features:    response.Gitaly.Features,
-	}
+	gc := handler.NewGitalyCommand(c.Config, string(commandargs.ReceivePack), response)
 
 	request := &pb.SSHReceivePackRequest{
 		Repository:       &response.Gitaly.Repo,
@@ -30,8 +24,8 @@
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
 		rw := c.ReadWriter
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -13,18 +13,12 @@
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
-	gc := &handler.GitalyCommand{
-		Config:      c.Config,
-		ServiceName: string(commandargs.UploadArchive),
-		Address:     response.Gitaly.Address,
-		Token:       response.Gitaly.Token,
-		Features:    response.Gitaly.Features,
-	}
+	gc := handler.NewGitalyCommand(c.Config, string(commandargs.UploadArchive), response)
 
 	request := &pb.SSHUploadArchiveRequest{Repository: &response.Gitaly.Repo}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
 		rw := c.ReadWriter
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -13,26 +13,20 @@
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
-	gc := &handler.GitalyCommand{
-		Config:      c.Config,
-		ServiceName: string(commandargs.UploadPack),
-		Address:     response.Gitaly.Address,
-		Token:       response.Gitaly.Token,
-		Features:    response.Gitaly.Features,
-	}
+	gc := handler.NewGitalyCommand(c.Config, string(commandargs.UploadPack), response)
 
 	if response.Gitaly.UseSidechannel {
-		gc.DialSidechannel = true
 		request := &pb.SSHUploadPackWithSidechannelRequest{
 			Repository:       &response.Gitaly.Repo,
 			GitProtocol:      c.Args.Env.GitProtocolVersion,
 			GitConfigOptions: response.GitConfigOptions,
 		}
 
-		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-			ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+			ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 			defer cancel()
 
+			registry := c.Config.GitalyClient.SidechannelRegistry
 			rw := c.ReadWriter
 			return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
 		})
@@ -44,8 +38,8 @@
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
 		rw := c.ReadWriter
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -97,15 +97,18 @@
 		Env:         env,
 	}
 
+	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
+	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
+
+	cfg := &config.Config{GitlabUrl: url}
+	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+
 	cmd := &Command{
-		Config:     &config.Config{GitlabUrl: url},
+		Config:     cfg,
 		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
 
-	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
-	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
-
 	err := cmd.Execute(ctx)
 	require.NoError(t, err)
 
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -12,6 +12,7 @@
 	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
 
@@ -59,6 +60,8 @@
 	httpClient     *client.HttpClient
 	httpClientErr  error
 	httpClientOnce sync.Once
+
+	GitalyClient gitaly.Client
 }
 
 // The defaults to apply before parsing the config file(s).
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
new file mode 100644
--- /dev/null
+++ b/internal/gitaly/gitaly.go
@@ -0,0 +1,133 @@
+package gitaly
+
+import (
+	"context"
+	"fmt"
+	"sync"
+
+	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
+	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
+	"google.golang.org/grpc"
+
+	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
+	"gitlab.com/gitlab-org/gitaly/v14/client"
+	gitalyclient "gitlab.com/gitlab-org/gitaly/v14/client"
+	"gitlab.com/gitlab-org/labkit/correlation"
+	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
+	"gitlab.com/gitlab-org/labkit/log"
+	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+)
+
+type Command struct {
+	ServiceName     string
+	Address         string
+	Token           string
+	DialSidechannel bool
+}
+
+type connectionsCache struct {
+	sync.RWMutex
+
+	connections map[Command]*grpc.ClientConn
+}
+
+type Client struct {
+	SidechannelRegistry *gitalyclient.SidechannelRegistry
+
+	cache connectionsCache
+}
+
+func (c *Client) InitSidechannelRegistry(ctx context.Context) {
+	c.SidechannelRegistry = gitalyclient.NewSidechannelRegistry(log.ContextLogger(ctx))
+}
+
+func (c *Client) GetConnection(ctx context.Context, cmd Command) (*grpc.ClientConn, error) {
+	c.cache.RLock()
+	conn := c.cache.connections[cmd]
+	c.cache.RUnlock()
+
+	if conn != nil {
+		return conn, nil
+	}
+
+	c.cache.Lock()
+	defer c.cache.Unlock()
+
+	if conn := c.cache.connections[cmd]; conn != nil {
+		return conn, nil
+	}
+
+	conn, err := c.newConnection(ctx, cmd)
+	if err != nil {
+		return nil, err
+	}
+
+	if c.cache.connections == nil {
+		c.cache.connections = make(map[Command]*grpc.ClientConn)
+	}
+
+	c.cache.connections[cmd] = conn
+
+	return conn, nil
+}
+
+func (c *Client) newConnection(ctx context.Context, cmd Command) (conn *grpc.ClientConn, err error) {
+	defer func() {
+		label := "ok"
+		if err != nil {
+			label = "fail"
+		}
+		metrics.GitalyConnectionsTotal.WithLabelValues(label).Inc()
+	}()
+
+	if cmd.Address == "" {
+		return nil, fmt.Errorf("no gitaly_address given")
+	}
+
+	serviceName := correlation.ExtractClientNameFromContext(ctx)
+	if serviceName == "" {
+		serviceName = "gitlab-shell-unknown"
+
+		log.WithContextFields(ctx, log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
+	}
+
+	serviceName = fmt.Sprintf("%s-%s", serviceName, cmd.ServiceName)
+
+	connOpts := client.DefaultDialOpts
+	connOpts = append(
+		connOpts,
+		grpc.WithStreamInterceptor(
+			grpc_middleware.ChainStreamClient(
+				grpctracing.StreamClientTracingInterceptor(),
+				grpc_prometheus.StreamClientInterceptor,
+				grpccorrelation.StreamClientCorrelationInterceptor(
+					grpccorrelation.WithClientName(serviceName),
+				),
+			),
+		),
+
+		grpc.WithUnaryInterceptor(
+			grpc_middleware.ChainUnaryClient(
+				grpctracing.UnaryClientTracingInterceptor(),
+				grpc_prometheus.UnaryClientInterceptor,
+				grpccorrelation.UnaryClientCorrelationInterceptor(
+					grpccorrelation.WithClientName(serviceName),
+				),
+			),
+		),
+	)
+
+	if cmd.Token != "" {
+		connOpts = append(connOpts,
+			grpc.WithPerRPCCredentials(gitalyauth.RPCCredentialsV2(cmd.Token)),
+		)
+	}
+
+	if cmd.DialSidechannel {
+		return client.DialSidechannel(ctx, cmd.Address, c.SidechannelRegistry, connOpts)
+	}
+
+	return client.DialContext(ctx, cmd.Address, connOpts)
+}
diff --git a/internal/gitaly/gitaly_test.go b/internal/gitaly/gitaly_test.go
new file mode 100644
--- /dev/null
+++ b/internal/gitaly/gitaly_test.go
@@ -0,0 +1,53 @@
+package gitaly
+
+import (
+	"context"
+	"testing"
+
+	"github.com/prometheus/client_golang/prometheus/testutil"
+	"github.com/stretchr/testify/require"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+)
+
+func TestPrometheusMetrics(t *testing.T) {
+	metrics.GitalyConnectionsTotal.Reset()
+
+	c := &Client{}
+
+	cmd := Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9999"}
+	c.newConnection(context.Background(), cmd)
+	c.newConnection(context.Background(), cmd)
+
+	require.Equal(t, 1, testutil.CollectAndCount(metrics.GitalyConnectionsTotal))
+	require.InDelta(t, 2, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("ok")), 0.1)
+	require.InDelta(t, 0, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("fail")), 0.1)
+
+	cmd = Command{Address: ""}
+	c.newConnection(context.Background(), cmd)
+
+	require.InDelta(t, 2, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("ok")), 0.1)
+	require.InDelta(t, 1, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("fail")), 0.1)
+}
+
+func TestCachedConnections(t *testing.T) {
+	c := &Client{}
+
+	require.Len(t, c.cache.connections, 0)
+
+	cmd := Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9999"}
+
+	conn, err := c.GetConnection(context.Background(), cmd)
+	require.NoError(t, err)
+	require.Len(t, c.cache.connections, 1)
+
+	newConn, err := c.GetConnection(context.Background(), cmd)
+	require.NoError(t, err)
+	require.Len(t, c.cache.connections, 1)
+	require.Equal(t, conn, newConn)
+
+	cmd = Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9998"}
+	_, err = c.GetConnection(context.Background(), cmd)
+	require.NoError(t, err)
+	require.Len(t, c.cache.connections, 2)
+}
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -6,56 +6,57 @@
 	"strconv"
 	"strings"
 
-	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
-	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
 	"google.golang.org/grpc"
 	grpccodes "google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 
-	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
-	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/labkit/correlation"
-	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
 	"gitlab.com/gitlab-org/labkit/log"
-	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
 )
 
 // GitalyHandlerFunc implementations are responsible for making
 // an appropriate Gitaly call using the provided client and context
 // and returning an error from the Gitaly call.
-type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error)
+type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn) (int32, error)
 
 type GitalyCommand struct {
-	Config          *config.Config
-	ServiceName     string
-	Address         string
-	Token           string
-	Features        map[string]string
-	DialSidechannel bool
+	Config   *config.Config
+	Response *accessverifier.Response
+	Command  gitaly.Command
+}
+
+func NewGitalyCommand(cfg *config.Config, serviceName string, response *accessverifier.Response) *GitalyCommand {
+	gc := gitaly.Command{
+		ServiceName:     serviceName,
+		Address:         response.Gitaly.Address,
+		Token:           response.Gitaly.Token,
+		DialSidechannel: response.Gitaly.UseSidechannel,
+	}
+
+	return &GitalyCommand{Config: cfg, Response: response, Command: gc}
 }
 
 // RunGitalyCommand provides a bootstrap for Gitaly commands executed
 // through GitLab-Shell. It ensures that logging, tracing and other
 // common concerns are configured before executing the `handler`.
 func (gc *GitalyCommand) RunGitalyCommand(ctx context.Context, handler GitalyHandlerFunc) error {
-	registry := client.NewSidechannelRegistry(log.ContextLogger(ctx))
-	conn, err := getConn(ctx, gc, registry)
+	// We leave the connection open for future reuse
+	conn, err := gc.getConn(ctx)
 	if err != nil {
 		log.ContextLogger(ctx).WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Failed to get connection to execute Git command")
 
 		return err
 	}
-	defer conn.Close()
 
-	childCtx := withOutgoingMetadata(ctx, gc.Features)
+	childCtx := withOutgoingMetadata(ctx, gc.Response.Gitaly.Features)
 	ctxlog := log.ContextLogger(childCtx)
-	exitStatus, err := handler(childCtx, conn, registry)
+	exitStatus, err := handler(childCtx, conn)
 
 	if err != nil {
 		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
@@ -72,35 +73,35 @@
 
 // PrepareContext wraps a given context with a correlation ID and logs the command to
 // be run.
-func (gc *GitalyCommand) PrepareContext(ctx context.Context, repository *pb.Repository, response *accessverifier.Response, env sshenv.Env) (context.Context, context.CancelFunc) {
+func (gc *GitalyCommand) PrepareContext(ctx context.Context, repository *pb.Repository, env sshenv.Env) (context.Context, context.CancelFunc) {
 	ctx, cancel := context.WithCancel(ctx)
-	gc.LogExecution(ctx, repository, response, env)
+	gc.LogExecution(ctx, repository, env)
 
 	md, ok := metadata.FromOutgoingContext(ctx)
 	if !ok {
 		md = metadata.New(nil)
 	}
-	md.Append("key_id", strconv.Itoa(response.KeyId))
-	md.Append("key_type", response.KeyType)
-	md.Append("user_id", response.UserId)
-	md.Append("username", response.Username)
+	md.Append("key_id", strconv.Itoa(gc.Response.KeyId))
+	md.Append("key_type", gc.Response.KeyType)
+	md.Append("user_id", gc.Response.UserId)
+	md.Append("username", gc.Response.Username)
 	md.Append("remote_ip", env.RemoteAddr)
 	ctx = metadata.NewOutgoingContext(ctx, md)
 
 	return ctx, cancel
 }
 
-func (gc *GitalyCommand) LogExecution(ctx context.Context, repository *pb.Repository, response *accessverifier.Response, env sshenv.Env) {
+func (gc *GitalyCommand) LogExecution(ctx context.Context, repository *pb.Repository, env sshenv.Env) {
 	fields := log.Fields{
-		"command":         gc.ServiceName,
+		"command":         gc.Command.ServiceName,
 		"gl_project_path": repository.GlProjectPath,
 		"gl_repository":   repository.GlRepository,
-		"user_id":         response.UserId,
-		"username":        response.Username,
+		"user_id":         gc.Response.UserId,
+		"username":        gc.Response.Username,
 		"git_protocol":    env.GitProtocolVersion,
 		"remote_ip":       env.RemoteAddr,
-		"gl_key_type":     response.KeyType,
-		"gl_key_id":       response.KeyId,
+		"gl_key_type":     gc.Response.KeyType,
+		"gl_key_id":       gc.Response.KeyId,
 	}
 
 	log.WithContextFields(ctx, fields).Info("executing git command")
@@ -118,53 +119,6 @@
 	return metadata.NewOutgoingContext(ctx, md)
 }
 
-func getConn(ctx context.Context, gc *GitalyCommand, registry *client.SidechannelRegistry) (*grpc.ClientConn, error) {
-	if gc.Address == "" {
-		return nil, fmt.Errorf("no gitaly_address given")
-	}
-
-	serviceName := correlation.ExtractClientNameFromContext(ctx)
-	if serviceName == "" {
-		serviceName = "gitlab-shell-unknown"
-
-		log.WithContextFields(ctx, log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
-	}
-
-	serviceName = fmt.Sprintf("%s-%s", serviceName, gc.ServiceName)
-
-	connOpts := client.DefaultDialOpts
-	connOpts = append(
-		connOpts,
-		grpc.WithStreamInterceptor(
-			grpc_middleware.ChainStreamClient(
-				grpctracing.StreamClientTracingInterceptor(),
-				grpc_prometheus.StreamClientInterceptor,
-				grpccorrelation.StreamClientCorrelationInterceptor(
-					grpccorrelation.WithClientName(serviceName),
-				),
-			),
-		),
-
-		grpc.WithUnaryInterceptor(
-			grpc_middleware.ChainUnaryClient(
-				grpctracing.UnaryClientTracingInterceptor(),
-				grpc_prometheus.UnaryClientInterceptor,
-				grpccorrelation.UnaryClientCorrelationInterceptor(
-					grpccorrelation.WithClientName(serviceName),
-				),
-			),
-		),
-	)
-
-	if gc.Token != "" {
-		connOpts = append(connOpts,
-			grpc.WithPerRPCCredentials(gitalyauth.RPCCredentialsV2(gc.Token)),
-		)
-	}
-
-	if gc.DialSidechannel {
-		return client.DialSidechannel(ctx, gc.Address, registry, connOpts)
-	}
-
-	return client.DialContext(ctx, gc.Address, connOpts)
+func (gc *GitalyCommand) getConn(ctx context.Context) (*grpc.ClientConn, error) {
+	return gc.Config.GitalyClient.GetConnection(ctx, gc.Command)
 }
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -11,15 +11,15 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
-func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn, *client.SidechannelRegistry) (int32, error) {
-	return func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
+func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn) (int32, error) {
+	return func(ctx context.Context, client *grpc.ClientConn) (int32, error) {
 		require.NotNil(t, ctx)
 		require.NotNil(t, client)
 
@@ -28,10 +28,13 @@
 }
 
 func TestRunGitalyCommand(t *testing.T) {
-	cmd := GitalyCommand{
-		Config:  &config.Config{},
-		Address: "tcp://localhost:9999",
-	}
+	cmd := NewGitalyCommand(
+		&config.Config{},
+		string(commandargs.UploadPack),
+		&accessverifier.Response{
+			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
+		},
+	)
 
 	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, nil))
 	require.NoError(t, err)
@@ -41,6 +44,32 @@
 	require.Equal(t, err, expectedErr)
 }
 
+func TestCachingOfGitalyConnections(t *testing.T) {
+	ctx := context.Background()
+	cfg := &config.Config{}
+	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+	response := &accessverifier.Response{
+		Username: "user",
+		Gitaly: accessverifier.Gitaly{
+			Address:        "tcp://localhost:9999",
+			Token:          "token",
+			UseSidechannel: true,
+		},
+	}
+
+	cmd := NewGitalyCommand(cfg, string(commandargs.UploadPack), response)
+
+	conn, err := cmd.getConn(ctx)
+	require.NoError(t, err)
+
+	// Reuses connection for different users
+	response.Username = "another-user"
+	cmd = NewGitalyCommand(cfg, string(commandargs.UploadPack), response)
+	newConn, err := cmd.getConn(ctx)
+	require.NoError(t, err)
+	require.Equal(t, conn, newConn)
+}
+
 func TestMissingGitalyAddress(t *testing.T) {
 	cmd := GitalyCommand{Config: &config.Config{}}
 
@@ -49,10 +78,13 @@
 }
 
 func TestUnavailableGitalyErr(t *testing.T) {
-	cmd := GitalyCommand{
-		Config:  &config.Config{},
-		Address: "tcp://localhost:9999",
-	}
+	cmd := NewGitalyCommand(
+		&config.Config{},
+		string(commandargs.UploadPack),
+		&accessverifier.Response{
+			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
+		},
+	)
 
 	expectedErr := grpcstatus.Error(grpccodes.Unavailable, "error")
 	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, expectedErr))
@@ -68,15 +100,20 @@
 	}{
 		{
 			name: "gitaly_feature_flags",
-			gc: &GitalyCommand{
-				Config:  &config.Config{},
-				Address: "tcp://localhost:9999",
-				Features: map[string]string{
-					"gitaly-feature-cache_invalidator":        "true",
-					"other-ff":                                "true",
-					"gitaly-feature-inforef_uploadpack_cache": "false",
+			gc: NewGitalyCommand(
+				&config.Config{},
+				string(commandargs.UploadPack),
+				&accessverifier.Response{
+					Gitaly: accessverifier.Gitaly{
+						Address: "tcp://localhost:9999",
+						Features: map[string]string{
+							"gitaly-feature-cache_invalidator":        "true",
+							"other-ff":                                "true",
+							"gitaly-feature-inforef_uploadpack_cache": "false",
+						},
+					},
 				},
-			},
+			),
 			want: map[string]string{
 				"gitaly-feature-cache_invalidator":        "true",
 				"gitaly-feature-inforef_uploadpack_cache": "false",
@@ -87,7 +124,7 @@
 		t.Run(tt.name, func(t *testing.T) {
 			cmd := tt.gc
 
-			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn, _ *client.SidechannelRegistry) (int32, error) {
+			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn) (int32, error) {
 				md, exists := metadata.FromOutgoingContext(ctx)
 				require.True(t, exists)
 				require.Equal(t, len(tt.want), md.Len())
@@ -117,10 +154,19 @@
 	}{
 		{
 			name: "client_identity",
-			gc: &GitalyCommand{
-				Config:  &config.Config{},
-				Address: "tcp://localhost:9999",
-			},
+			gc: NewGitalyCommand(
+				&config.Config{},
+				string(commandargs.UploadPack),
+				&accessverifier.Response{
+					KeyId:    1,
+					KeyType:  "key",
+					UserId:   "6",
+					Username: "jane.doe",
+					Gitaly: accessverifier.Gitaly{
+						Address: "tcp://localhost:9999",
+					},
+				},
+			),
 			env: sshenv.Env{
 				GitProtocolVersion: "protocol",
 				IsSSHConnection:    true,
@@ -134,12 +180,6 @@
 				GlRepository:                  "project-26",
 				GlProjectPath:                 "group/private",
 			},
-			response: &accessverifier.Response{
-				KeyId:    1,
-				KeyType:  "key",
-				UserId:   "6",
-				Username: "jane.doe",
-			},
 			want: map[string]string{
 				"key_id":    "1",
 				"key_type":  "key",
@@ -153,7 +193,7 @@
 		t.Run(tt.name, func(t *testing.T) {
 			ctx := context.Background()
 
-			ctx, cancel := tt.gc.PrepareContext(ctx, tt.repo, tt.response, tt.env)
+			ctx, cancel := tt.gc.PrepareContext(ctx, tt.repo, tt.env)
 			defer cancel()
 
 			md, exists := metadata.FromOutgoingContext(ctx)
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -9,9 +9,10 @@
 )
 
 const (
-	namespace     = "gitlab_shell"
-	sshdSubsystem = "sshd"
-	httpSubsystem = "http"
+	namespace       = "gitlab_shell"
+	sshdSubsystem   = "sshd"
+	httpSubsystem   = "http"
+	gitalySubsystem = "gitaly"
 
 	httpInFlightRequestsMetricName       = "in_flight_requests"
 	httpRequestsTotalMetricName          = "requests_total"
@@ -20,6 +21,8 @@
 	sshdConnectionsInFlightName = "in_flight_connections"
 	sshdConnectionDuration      = "connection_duration_seconds"
 	sshdHitMaxSessions          = "concurrent_limited_sessions_total"
+
+	gitalyConnectionsTotalName = "connections_total"
 )
 
 var (
@@ -61,6 +64,16 @@
 		},
 	)
 
+	GitalyConnectionsTotal = promauto.NewCounterVec(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: gitalySubsystem,
+			Name:      gitalyConnectionsTotalName,
+			Help:      "Number of Gitaly connections that have been established",
+		},
+		[]string{"status"},
+	)
+
 	// The metrics and the buckets size are similar to the ones we have for handlers in Labkit
 	// When the MR: https://gitlab.com/gitlab-org/labkit/-/merge_requests/150 is merged,
 	// these metrics can be refactored out of Gitlab Shell code by using the helper function from Labkit
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1647615010 -10800
#      Fri Mar 18 17:50:10 2022 +0300
# Node ID bab1c219318e2954384d384e53d4db2bb221fd59
# Parent  80642f2839758d20b0e5a87fb36460e1a9e6e0bf
Release 13.24.1

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+v13.24.1
+
+- Default to info level for an empty log-level !579
+- Update Gitaly dependency to v14.9.0-rc1 !578
+- Reuse Gitaly connections and sidechannel !575
+
 v13.24.0
 
 - Upgrade golang to 1.17.7 !576
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.24.0
+13.24.1
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1647615461 0
#      Fri Mar 18 14:57:41 2022 +0000
# Node ID b4a5351f09c840d4e1d17cd2a3aac596b875829c
# Parent  80642f2839758d20b0e5a87fb36460e1a9e6e0bf
# Parent  bab1c219318e2954384d384e53d4db2bb221fd59
Merge branch 'id-release-v-13-24-1' into 'main'

Release 13.24.1

See merge request gitlab-org/gitlab-shell!580

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+v13.24.1
+
+- Default to info level for an empty log-level !579
+- Update Gitaly dependency to v14.9.0-rc1 !578
+- Reuse Gitaly connections and sidechannel !575
+
 v13.24.0
 
 - Upgrade golang to 1.17.7 !576
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.24.0
+13.24.1
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1648165224 0
#      Thu Mar 24 23:40:24 2022 +0000
# Node ID ffeae5042b1188817e8e83c66735fae5976257ce
# Parent  b4a5351f09c840d4e1d17cd2a3aac596b875829c
Add docs for grace-period and probes config options

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -78,6 +78,12 @@
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
+  # The server waits for this time (in seconds) for the ongoing connections to complete before shutting down. Defaults to 10.
+  grace_period: 10
+  # The endpoint that returns 200 OK if the server is ready to receive incoming connections; otherwise, it returns 503 Service Unavailable. Defaults to "/start".
+  readiness_probe: "/start"
+  # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
+  liveness_probe: "/health"
   # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1648165224 0
#      Thu Mar 24 23:40:24 2022 +0000
# Node ID 9152c3a4030722b823f663307ae59781cd40f9e2
# Parent  b4a5351f09c840d4e1d17cd2a3aac596b875829c
# Parent  ffeae5042b1188817e8e83c66735fae5976257ce
Merge branch 'id-config-fields-docs' into 'main'

Add docs for grace-period and probes config options

See merge request gitlab-org/gitlab-shell!581

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -78,6 +78,12 @@
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
+  # The server waits for this time (in seconds) for the ongoing connections to complete before shutting down. Defaults to 10.
+  grace_period: 10
+  # The endpoint that returns 200 OK if the server is ready to receive incoming connections; otherwise, it returns 503 Service Unavailable. Defaults to "/start".
+  readiness_probe: "/start"
+  # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
+  liveness_probe: "/health"
   # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
# HG changeset patch
# User Jacob Vosmaer <jacob@gitlab.com>
# Date 1648557348 0
#      Tue Mar 29 12:35:48 2022 +0000
# Node ID 5f7a2441e28f8c095ef8a07c5ff688a111d6a76f
# Parent  9152c3a4030722b823f663307ae59781cd40f9e2
Bump gitaly client

Bump gitaly client to 51da8bc17059e4ccd39873e4f3def935341472b8 to get
changes from
https://gitlab.com/gitlab-org/gitaly/-/merge_requests/4439.

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@
 	github.com/pires/go-proxyproto v0.6.1
 	github.com/prometheus/client_golang v1.11.0
 	github.com/stretchr/testify v1.7.0
-	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc1
+	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
 	gitlab.com/gitlab-org/labkit v1.12.0
 	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -585,7 +585,7 @@
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
 github.com/libgit2/git2go/v31 v31.4.12/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec=
-github.com/libgit2/git2go/v33 v33.0.6/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
+github.com/libgit2/git2go/v33 v33.0.9/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI=
@@ -867,8 +867,8 @@
 gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
 gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
 gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
-gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc1 h1:9vStRdXxcBQ8dHlVnpV28fwLOgyDkSFIpGnPqwzdTvw=
-gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc1/go.mod h1:Xk5pn6IWsejg3z2X6BRczC5QaI97PRF3GU5OrJ5Amkg=
+gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059 h1:X7+3GQIxUpScXpIMCU5+sfpYvZyBIQ3GMlEosP7Jssw=
+gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80 h1:rVzTLlEDlX/ocWqNxeuxb9XklLzmI9FdP0teRqkSg00=
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1648558269 0
#      Tue Mar 29 12:51:09 2022 +0000
# Node ID b58720b5e49212484119bfbe0aff2f0c2726cd92
# Parent  9152c3a4030722b823f663307ae59781cd40f9e2
# Parent  5f7a2441e28f8c095ef8a07c5ff688a111d6a76f
Merge branch 'jv-bump-gitaly' into 'main'

Bump gitaly client

See merge request gitlab-org/gitlab-shell!584

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@
 	github.com/pires/go-proxyproto v0.6.1
 	github.com/prometheus/client_golang v1.11.0
 	github.com/stretchr/testify v1.7.0
-	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc1
+	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
 	gitlab.com/gitlab-org/labkit v1.12.0
 	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -585,7 +585,7 @@
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
 github.com/libgit2/git2go/v31 v31.4.12/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec=
-github.com/libgit2/git2go/v33 v33.0.6/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
+github.com/libgit2/git2go/v33 v33.0.9/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI=
@@ -867,8 +867,8 @@
 gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
 gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
 gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
-gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc1 h1:9vStRdXxcBQ8dHlVnpV28fwLOgyDkSFIpGnPqwzdTvw=
-gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc1/go.mod h1:Xk5pn6IWsejg3z2X6BRczC5QaI97PRF3GU5OrJ5Amkg=
+gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059 h1:X7+3GQIxUpScXpIMCU5+sfpYvZyBIQ3GMlEosP7Jssw=
+gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80 h1:rVzTLlEDlX/ocWqNxeuxb9XklLzmI9FdP0teRqkSg00=
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1648559332 -10800
#      Tue Mar 29 16:08:52 2022 +0300
# Node ID 917c5038c617e2e4e6c0bd9c31396695671cd949
# Parent  b58720b5e49212484119bfbe0aff2f0c2726cd92
Release 13.24.2

- Bump gitaly client !584

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v13.24.2
+
+- Bump gitaly client !584
+
 v13.24.1
 
 - Default to info level for an empty log-level !579
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1648559902 0
#      Tue Mar 29 13:18:22 2022 +0000
# Node ID c1490bc3ce020ca4971628342e390b7e9f4c2423
# Parent  b58720b5e49212484119bfbe0aff2f0c2726cd92
# Parent  917c5038c617e2e4e6c0bd9c31396695671cd949
Merge branch 'id-release-13-24-2' into 'main'

Release 13.24.2

See merge request gitlab-org/gitlab-shell!585

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v13.24.2
+
+- Bump gitaly client !584
+
 v13.24.1
 
 - Default to info level for an empty log-level !579
# HG changeset patch
# User Rémy Coutable <remy@rymai.me>
# Date 1648545694 -7200
#      Tue Mar 29 11:21:34 2022 +0200
# Node ID 25a0daf39d2edcc23b60d850c921d62b624d3363
# Parent  9152c3a4030722b823f663307ae59781cd40f9e2
Use gitlab-dangerfiles to improve MR hygiene

Signed-off-by: Rémy Coutable <remy@rymai.me>

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,6 +3,9 @@
   - template: Security/SAST.gitlab-ci.yml
   - template: Security/Dependency-Scanning.gitlab-ci.yml
   - template: Security/Secret-Detection.gitlab-ci.yml
+  - project: 'gitlab-org/quality/pipeline-common'
+    file:
+      - '/ci/danger-review.yml'
 
 variables:
   DOCKER_VERSION: "20.10.3"
diff --git a/Dangerfile b/Dangerfile
new file mode 100644
--- /dev/null
+++ b/Dangerfile
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'gitlab-dangerfiles'
+
+Gitlab::Dangerfiles.for_project(self) do |gitlab_dangerfiles|
+  gitlab_dangerfiles.import_plugins
+
+  gitlab_dangerfiles.import_dangerfiles(except: %w[changelog commit_messages simple_roulette])
+end
diff --git a/Gemfile b/Gemfile
--- a/Gemfile
+++ b/Gemfile
@@ -3,3 +3,7 @@
 group :development, :test do
   gem 'rspec', '~> 3.8.0'
 end
+
+group :development, :danger do
+  gem 'gitlab-dangerfiles', '~> 3.0.0'
+end
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,7 +1,89 @@
 GEM
   remote: https://rubygems.org/
   specs:
+    addressable (2.8.0)
+      public_suffix (>= 2.0.2, < 5.0)
+    claide (1.1.0)
+    claide-plugins (0.9.2)
+      cork
+      nap
+      open4 (~> 1.3)
+    colored2 (3.1.2)
+    cork (0.3.0)
+      colored2 (~> 3.1)
+    danger (8.5.0)
+      claide (~> 1.0)
+      claide-plugins (>= 0.9.2)
+      colored2 (~> 3.1)
+      cork (~> 0.1)
+      faraday (>= 0.9.0, < 2.0)
+      faraday-http-cache (~> 2.0)
+      git (~> 1.7)
+      kramdown (~> 2.3)
+      kramdown-parser-gfm (~> 1.0)
+      no_proxy_fix
+      octokit (~> 4.7)
+      terminal-table (>= 1, < 4)
+    danger-gitlab (8.0.0)
+      danger
+      gitlab (~> 4.2, >= 4.2.0)
     diff-lcs (1.3)
+    faraday (1.10.0)
+      faraday-em_http (~> 1.0)
+      faraday-em_synchrony (~> 1.0)
+      faraday-excon (~> 1.1)
+      faraday-httpclient (~> 1.0)
+      faraday-multipart (~> 1.0)
+      faraday-net_http (~> 1.0)
+      faraday-net_http_persistent (~> 1.0)
+      faraday-patron (~> 1.0)
+      faraday-rack (~> 1.0)
+      faraday-retry (~> 1.0)
+      ruby2_keywords (>= 0.0.4)
+    faraday-em_http (1.0.0)
+    faraday-em_synchrony (1.0.0)
+    faraday-excon (1.1.0)
+    faraday-http-cache (2.2.0)
+      faraday (>= 0.8)
+    faraday-httpclient (1.0.1)
+    faraday-multipart (1.0.3)
+      multipart-post (>= 1.2, < 3)
+    faraday-net_http (1.0.1)
+    faraday-net_http_persistent (1.2.0)
+    faraday-patron (1.0.0)
+    faraday-rack (1.0.0)
+    faraday-retry (1.0.3)
+    git (1.10.2)
+      rchardet (~> 1.8)
+    gitlab (4.18.0)
+      httparty (~> 0.18)
+      terminal-table (>= 1.5.1)
+    gitlab-dangerfiles (3.0.0)
+      danger (>= 8.4.5)
+      danger-gitlab (>= 8.0.0)
+      rake
+    httparty (0.20.0)
+      mime-types (~> 3.0)
+      multi_xml (>= 0.5.2)
+    kramdown (2.3.2)
+      rexml
+    kramdown-parser-gfm (1.1.0)
+      kramdown (~> 2.0)
+    mime-types (3.4.1)
+      mime-types-data (~> 3.2015)
+    mime-types-data (3.2022.0105)
+    multi_xml (0.6.0)
+    multipart-post (2.1.1)
+    nap (1.1.0)
+    no_proxy_fix (0.1.2)
+    octokit (4.22.0)
+      faraday (>= 0.9)
+      sawyer (~> 0.8.0, >= 0.5.3)
+    open4 (1.3.4)
+    public_suffix (4.0.6)
+    rake (13.0.6)
+    rchardet (1.8.0)
+    rexml (3.2.5)
     rspec (3.8.0)
       rspec-core (~> 3.8.0)
       rspec-expectations (~> 3.8.0)
@@ -15,11 +97,19 @@
       diff-lcs (>= 1.2.0, < 2.0)
       rspec-support (~> 3.8.0)
     rspec-support (3.8.0)
+    ruby2_keywords (0.0.5)
+    sawyer (0.8.2)
+      addressable (>= 2.3.5)
+      faraday (> 0.8, < 2.0)
+    terminal-table (3.0.2)
+      unicode-display_width (>= 1.1.1, < 3)
+    unicode-display_width (2.1.0)
 
 PLATFORMS
   ruby
 
 DEPENDENCIES
+  gitlab-dangerfiles (~> 3.0.0)
   rspec (~> 3.8.0)
 
 BUNDLED WITH
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1648625945 0
#      Wed Mar 30 07:39:05 2022 +0000
# Node ID ee32473559d6881f8f895afe5d4714544bf129d7
# Parent  c1490bc3ce020ca4971628342e390b7e9f4c2423
# Parent  25a0daf39d2edcc23b60d850c921d62b624d3363
Merge branch 'use-gitlab-dangerfiles' into 'main'

Use gitlab-dangerfiles to improve MR hygiene

See merge request gitlab-org/gitlab-shell!583

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,6 +3,9 @@
   - template: Security/SAST.gitlab-ci.yml
   - template: Security/Dependency-Scanning.gitlab-ci.yml
   - template: Security/Secret-Detection.gitlab-ci.yml
+  - project: 'gitlab-org/quality/pipeline-common'
+    file:
+      - '/ci/danger-review.yml'
 
 variables:
   DOCKER_VERSION: "20.10.3"
diff --git a/Dangerfile b/Dangerfile
new file mode 100644
--- /dev/null
+++ b/Dangerfile
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'gitlab-dangerfiles'
+
+Gitlab::Dangerfiles.for_project(self) do |gitlab_dangerfiles|
+  gitlab_dangerfiles.import_plugins
+
+  gitlab_dangerfiles.import_dangerfiles(except: %w[changelog commit_messages simple_roulette])
+end
diff --git a/Gemfile b/Gemfile
--- a/Gemfile
+++ b/Gemfile
@@ -3,3 +3,7 @@
 group :development, :test do
   gem 'rspec', '~> 3.8.0'
 end
+
+group :development, :danger do
+  gem 'gitlab-dangerfiles', '~> 3.0.0'
+end
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,7 +1,89 @@
 GEM
   remote: https://rubygems.org/
   specs:
+    addressable (2.8.0)
+      public_suffix (>= 2.0.2, < 5.0)
+    claide (1.1.0)
+    claide-plugins (0.9.2)
+      cork
+      nap
+      open4 (~> 1.3)
+    colored2 (3.1.2)
+    cork (0.3.0)
+      colored2 (~> 3.1)
+    danger (8.5.0)
+      claide (~> 1.0)
+      claide-plugins (>= 0.9.2)
+      colored2 (~> 3.1)
+      cork (~> 0.1)
+      faraday (>= 0.9.0, < 2.0)
+      faraday-http-cache (~> 2.0)
+      git (~> 1.7)
+      kramdown (~> 2.3)
+      kramdown-parser-gfm (~> 1.0)
+      no_proxy_fix
+      octokit (~> 4.7)
+      terminal-table (>= 1, < 4)
+    danger-gitlab (8.0.0)
+      danger
+      gitlab (~> 4.2, >= 4.2.0)
     diff-lcs (1.3)
+    faraday (1.10.0)
+      faraday-em_http (~> 1.0)
+      faraday-em_synchrony (~> 1.0)
+      faraday-excon (~> 1.1)
+      faraday-httpclient (~> 1.0)
+      faraday-multipart (~> 1.0)
+      faraday-net_http (~> 1.0)
+      faraday-net_http_persistent (~> 1.0)
+      faraday-patron (~> 1.0)
+      faraday-rack (~> 1.0)
+      faraday-retry (~> 1.0)
+      ruby2_keywords (>= 0.0.4)
+    faraday-em_http (1.0.0)
+    faraday-em_synchrony (1.0.0)
+    faraday-excon (1.1.0)
+    faraday-http-cache (2.2.0)
+      faraday (>= 0.8)
+    faraday-httpclient (1.0.1)
+    faraday-multipart (1.0.3)
+      multipart-post (>= 1.2, < 3)
+    faraday-net_http (1.0.1)
+    faraday-net_http_persistent (1.2.0)
+    faraday-patron (1.0.0)
+    faraday-rack (1.0.0)
+    faraday-retry (1.0.3)
+    git (1.10.2)
+      rchardet (~> 1.8)
+    gitlab (4.18.0)
+      httparty (~> 0.18)
+      terminal-table (>= 1.5.1)
+    gitlab-dangerfiles (3.0.0)
+      danger (>= 8.4.5)
+      danger-gitlab (>= 8.0.0)
+      rake
+    httparty (0.20.0)
+      mime-types (~> 3.0)
+      multi_xml (>= 0.5.2)
+    kramdown (2.3.2)
+      rexml
+    kramdown-parser-gfm (1.1.0)
+      kramdown (~> 2.0)
+    mime-types (3.4.1)
+      mime-types-data (~> 3.2015)
+    mime-types-data (3.2022.0105)
+    multi_xml (0.6.0)
+    multipart-post (2.1.1)
+    nap (1.1.0)
+    no_proxy_fix (0.1.2)
+    octokit (4.22.0)
+      faraday (>= 0.9)
+      sawyer (~> 0.8.0, >= 0.5.3)
+    open4 (1.3.4)
+    public_suffix (4.0.6)
+    rake (13.0.6)
+    rchardet (1.8.0)
+    rexml (3.2.5)
     rspec (3.8.0)
       rspec-core (~> 3.8.0)
       rspec-expectations (~> 3.8.0)
@@ -15,11 +97,19 @@
       diff-lcs (>= 1.2.0, < 2.0)
       rspec-support (~> 3.8.0)
     rspec-support (3.8.0)
+    ruby2_keywords (0.0.5)
+    sawyer (0.8.2)
+      addressable (>= 2.3.5)
+      faraday (> 0.8, < 2.0)
+    terminal-table (3.0.2)
+      unicode-display_width (>= 1.1.1, < 3)
+    unicode-display_width (2.1.0)
 
 PLATFORMS
   ruby
 
 DEPENDENCIES
+  gitlab-dangerfiles (~> 3.0.0)
   rspec (~> 3.8.0)
 
 BUNDLED WITH
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1648628224 0
#      Wed Mar 30 08:17:04 2022 +0000
# Node ID aeac3ba0ed947c1e404d81b3b29f45c6061ea177
# Parent  ee32473559d6881f8f895afe5d4714544bf129d7
Abort long-running unauthenticated SSH connections

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -84,6 +84,8 @@
   readiness_probe: "/start"
   # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
   liveness_probe: "/health"
+  # The server disconnects after this time (in seconds) if the user has not successfully logged in. Defaults to 60.
+  login_grace_time: 60
   # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -26,6 +26,7 @@
 	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
 	WebListen               string   `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
+	LoginGraceTimeSeconds   uint64   `yaml:"login_grace_time"`
 	GracePeriodSeconds      uint64   `yaml:"grace_period"`
 	ReadinessProbe          string   `yaml:"readiness_probe"`
 	LivenessProbe           string   `yaml:"liveness_probe"`
@@ -78,6 +79,7 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
+		LoginGraceTimeSeconds:   60,
 		GracePeriodSeconds:      10,
 		ReadinessProbe:          "/start",
 		LivenessProbe:           "/health",
@@ -89,6 +91,10 @@
 	}
 )
 
+func (sc *ServerConfig) LoginGraceTime() time.Duration {
+	return time.Duration(sc.LoginGraceTimeSeconds) * time.Second
+}
+
 func (sc *ServerConfig) GracePeriod() time.Duration {
 	return time.Duration(sc.GracePeriodSeconds) * time.Second
 }
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -164,7 +164,7 @@
 
 	ctxlog.Info("server: handleConn: start")
 
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
+	sconn, chans, reqs, err := s.initSSHConnection(ctx, nconn)
 	if err != nil {
 		ctxlog.WithError(err).Error("server: handleConn: failed to initialize SSH connection")
 		return
@@ -187,6 +187,20 @@
 	ctxlog.Info("server: handleConn: done")
 }
 
+func (s *Server) initSSHConnection(ctx context.Context, nconn net.Conn) (sconn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request, err error) {
+	timer := time.AfterFunc(s.Config.Server.LoginGraceTime(), func() {
+		nconn.Close()
+	})
+	defer func() {
+		// If time.Stop() equals false, that means that AfterFunc has been executed
+		if !timer.Stop() {
+			err = fmt.Errorf("initSSHConnection: ssh handshake timeout")
+		}
+	}()
+
+	return ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
+}
+
 func unconditionalRequirePolicy(_ net.Addr) (proxyproto.Policy, error) {
 	return proxyproto.REQUIRE, nil
 }
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -3,6 +3,7 @@
 import (
 	"context"
 	"fmt"
+	"net"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -134,6 +135,33 @@
 	require.Nil(t, s.Shutdown())
 }
 
+func TestLoginGraceTime(t *testing.T) {
+	s := setupServer(t)
+
+	unauthenticatedRequestStatus := make(chan string)
+	completed := make(chan bool)
+
+	clientCfg := clientConfig(t)
+	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
+		unauthenticatedRequestStatus <- "authentication-started"
+		<-completed // Wait infinitely
+
+		return nil
+	}
+
+	go func() {
+		// Start an SSH connection that never ends
+		ssh.Dial("tcp", serverUrl, clientCfg)
+	}()
+
+	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
+
+	// Verify that the server shutdowns instantly without waiting for any connection to execute
+	// That means that the server doesn't have any connections to wait for
+	require.NoError(t, s.Shutdown())
+	verifyStatus(t, s, StatusClosed)
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
@@ -183,6 +211,7 @@
 	cfg.User = user
 	cfg.Server.Listen = serverUrl
 	cfg.Server.ConcurrentSessionsLimit = 1
+	cfg.Server.LoginGraceTimeSeconds = 1
 	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
 
 	s, err := NewServer(cfg)
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1648628224 0
#      Wed Mar 30 08:17:04 2022 +0000
# Node ID 25dad1ff6b70811873f8826160431ae92e07f231
# Parent  ee32473559d6881f8f895afe5d4714544bf129d7
# Parent  aeac3ba0ed947c1e404d81b3b29f45c6061ea177
Merge branch 'id-logic-grace-time' into 'main'

Abort long-running unauthenticated SSH connections

See merge request gitlab-org/gitlab-shell!582

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -84,6 +84,8 @@
   readiness_probe: "/start"
   # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
   liveness_probe: "/health"
+  # The server disconnects after this time (in seconds) if the user has not successfully logged in. Defaults to 60.
+  login_grace_time: 60
   # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -26,6 +26,7 @@
 	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
 	WebListen               string   `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
+	LoginGraceTimeSeconds   uint64   `yaml:"login_grace_time"`
 	GracePeriodSeconds      uint64   `yaml:"grace_period"`
 	ReadinessProbe          string   `yaml:"readiness_probe"`
 	LivenessProbe           string   `yaml:"liveness_probe"`
@@ -78,6 +79,7 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
+		LoginGraceTimeSeconds:   60,
 		GracePeriodSeconds:      10,
 		ReadinessProbe:          "/start",
 		LivenessProbe:           "/health",
@@ -89,6 +91,10 @@
 	}
 )
 
+func (sc *ServerConfig) LoginGraceTime() time.Duration {
+	return time.Duration(sc.LoginGraceTimeSeconds) * time.Second
+}
+
 func (sc *ServerConfig) GracePeriod() time.Duration {
 	return time.Duration(sc.GracePeriodSeconds) * time.Second
 }
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -164,7 +164,7 @@
 
 	ctxlog.Info("server: handleConn: start")
 
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
+	sconn, chans, reqs, err := s.initSSHConnection(ctx, nconn)
 	if err != nil {
 		ctxlog.WithError(err).Error("server: handleConn: failed to initialize SSH connection")
 		return
@@ -187,6 +187,20 @@
 	ctxlog.Info("server: handleConn: done")
 }
 
+func (s *Server) initSSHConnection(ctx context.Context, nconn net.Conn) (sconn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request, err error) {
+	timer := time.AfterFunc(s.Config.Server.LoginGraceTime(), func() {
+		nconn.Close()
+	})
+	defer func() {
+		// If time.Stop() equals false, that means that AfterFunc has been executed
+		if !timer.Stop() {
+			err = fmt.Errorf("initSSHConnection: ssh handshake timeout")
+		}
+	}()
+
+	return ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
+}
+
 func unconditionalRequirePolicy(_ net.Addr) (proxyproto.Policy, error) {
 	return proxyproto.REQUIRE, nil
 }
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -3,6 +3,7 @@
 import (
 	"context"
 	"fmt"
+	"net"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -134,6 +135,33 @@
 	require.Nil(t, s.Shutdown())
 }
 
+func TestLoginGraceTime(t *testing.T) {
+	s := setupServer(t)
+
+	unauthenticatedRequestStatus := make(chan string)
+	completed := make(chan bool)
+
+	clientCfg := clientConfig(t)
+	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
+		unauthenticatedRequestStatus <- "authentication-started"
+		<-completed // Wait infinitely
+
+		return nil
+	}
+
+	go func() {
+		// Start an SSH connection that never ends
+		ssh.Dial("tcp", serverUrl, clientCfg)
+	}()
+
+	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
+
+	// Verify that the server shutdowns instantly without waiting for any connection to execute
+	// That means that the server doesn't have any connections to wait for
+	require.NoError(t, s.Shutdown())
+	verifyStatus(t, s, StatusClosed)
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
@@ -183,6 +211,7 @@
 	cfg.User = user
 	cfg.Server.Listen = serverUrl
 	cfg.Server.ConcurrentSessionsLimit = 1
+	cfg.Server.LoginGraceTimeSeconds = 1
 	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
 
 	s, err := NewServer(cfg)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1648714502 -14400
#      Thu Mar 31 12:15:02 2022 +0400
# Node ID c31ba1247c79fc4654062188a9560673f8877cdc
# Parent  25dad1ff6b70811873f8826160431ae92e07f231
Improve login grace timeout message

diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -194,7 +194,7 @@
 	defer func() {
 		// If time.Stop() equals false, that means that AfterFunc has been executed
 		if !timer.Stop() {
-			err = fmt.Errorf("initSSHConnection: ssh handshake timeout")
+			err = fmt.Errorf("initSSHConnection: ssh handshake timeout of %v", s.Config.Server.LoginGraceTime())
 		}
 	}()
 
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1648774768 0
#      Fri Apr 01 00:59:28 2022 +0000
# Node ID a3d2ed5acfd10018e5e23fbcf782cc0b7dc48560
# Parent  25dad1ff6b70811873f8826160431ae92e07f231
# Parent  c31ba1247c79fc4654062188a9560673f8877cdc
Merge branch 'improve-login-grace-timeout-msg' into 'main'

Improve login grace timeout message

Closes #553

See merge request gitlab-org/gitlab-shell!586

diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -194,7 +194,7 @@
 	defer func() {
 		// If time.Stop() equals false, that means that AfterFunc has been executed
 		if !timer.Stop() {
-			err = fmt.Errorf("initSSHConnection: ssh handshake timeout")
+			err = fmt.Errorf("initSSHConnection: ssh handshake timeout of %v", s.Config.Server.LoginGraceTime())
 		}
 	}()
 
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1648815245 -7200
#      Fri Apr 01 14:14:05 2022 +0200
# Node ID 3bf27c48724cce6c96a3640bfa06c4fd1136296c
# Parent  a3d2ed5acfd10018e5e23fbcf782cc0b7dc48560
ci: start integrating go 1.18 into the CI pipelines

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -55,23 +55,19 @@
   script:
     - make verify test
 
-go:1.16:
+tests:
   extends: .test
-  image: golang:1.16
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
-go:1.17:
-  extends: .test
-  image: golang:1.17
+  image: golang:${GO_VERSION}
+  parallel:
+    matrix:
+      - GO_VERSION: ["1.16", "1.17", "1.18"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
 
 race:
   extends: .test
-  image: golang:1.17
+  image: golang:1.18
   script:
     - make test_golang_race
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1649218442 0
#      Wed Apr 06 04:14:02 2022 +0000
# Node ID 715513fde9e9b351e181d3063dfc236a29ba4b60
# Parent  a3d2ed5acfd10018e5e23fbcf782cc0b7dc48560
# Parent  3bf27c48724cce6c96a3640bfa06c4fd1136296c
Merge branch 'ci/go-1.18' into 'main'

ci: start integrating go 1.18 into the CI pipelines

See merge request gitlab-org/gitlab-shell!587

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -55,23 +55,19 @@
   script:
     - make verify test
 
-go:1.16:
+tests:
   extends: .test
-  image: golang:1.16
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
-go:1.17:
-  extends: .test
-  image: golang:1.17
+  image: golang:${GO_VERSION}
+  parallel:
+    matrix:
+      - GO_VERSION: ["1.16", "1.17", "1.18"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
 
 race:
   extends: .test
-  image: golang:1.17
+  image: golang:1.18
   script:
     - make test_golang_race
 
# HG changeset patch
# User Jason Goodman <jgoodman@gitlab.com>
# Date 1649218579 0
#      Wed Apr 06 04:16:19 2022 +0000
# Node ID 96addbc8b7783117167ef41d1fe6699b2bdb147c
# Parent  a3d2ed5acfd10018e5e23fbcf782cc0b7dc48560
Update Version File

diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.24.1
+13.24.2
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1649218581 0
#      Wed Apr 06 04:16:21 2022 +0000
# Node ID c28b67cac739dce1c4549b7ed746d3fd1688ed26
# Parent  715513fde9e9b351e181d3063dfc236a29ba4b60
# Parent  96addbc8b7783117167ef41d1fe6699b2bdb147c
Merge branch 'update-version-file' into 'main'

Update Version File

See merge request gitlab-org/gitlab-shell!589

diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.24.1
+13.24.2
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1649149365 -14400
#      Tue Apr 05 13:02:45 2022 +0400
# Node ID 24b6ef0325ddd63dca7a0763e057c3a3b5293a37
# Parent  a3d2ed5acfd10018e5e23fbcf782cc0b7dc48560
Fix connections duration metrics

We need to pass time.Now as a param, otherwise it's calculated on call

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -13,7 +13,6 @@
 )
 
 type connection struct {
-	begin              time.Time
 	concurrentSessions *semaphore.Weighted
 	remoteAddr         string
 }
@@ -22,7 +21,6 @@
 
 func newConnection(maxSessions int64, remoteAddr string) *connection {
 	return &connection{
-		begin:              time.Now(),
 		concurrentSessions: semaphore.NewWeighted(maxSessions),
 		remoteAddr:         remoteAddr,
 	}
@@ -32,9 +30,11 @@
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
 	metrics.SshdConnectionsInFlight.Inc()
-	defer metrics.SshdConnectionsInFlight.Dec()
 
-	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
+	defer func(started time.Time) {
+		metrics.SshdConnectionsInFlight.Dec()
+		metrics.SshdConnectionDuration.Observe(time.Since(started).Seconds())
+	}(time.Now())
 
 	for newChannel := range chans {
 		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1649230580 0
#      Wed Apr 06 07:36:20 2022 +0000
# Node ID 19b356eaefe883046e2d240533cfdb4096da2025
# Parent  c28b67cac739dce1c4549b7ed746d3fd1688ed26
# Parent  24b6ef0325ddd63dca7a0763e057c3a3b5293a37
Merge branch 'id-fix-connections-duration-metrics' into 'main'

Fix connections duration metrics

See merge request gitlab-org/gitlab-shell!588

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -13,7 +13,6 @@
 )
 
 type connection struct {
-	begin              time.Time
 	concurrentSessions *semaphore.Weighted
 	remoteAddr         string
 }
@@ -22,7 +21,6 @@
 
 func newConnection(maxSessions int64, remoteAddr string) *connection {
 	return &connection{
-		begin:              time.Now(),
 		concurrentSessions: semaphore.NewWeighted(maxSessions),
 		remoteAddr:         remoteAddr,
 	}
@@ -32,9 +30,11 @@
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
 	metrics.SshdConnectionsInFlight.Inc()
-	defer metrics.SshdConnectionsInFlight.Dec()
 
-	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
+	defer func(started time.Time) {
+		metrics.SshdConnectionsInFlight.Dec()
+		metrics.SshdConnectionDuration.Observe(time.Since(started).Seconds())
+	}(time.Now())
 
 	for newChannel := range chans {
 		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1649261638 -14400
#      Wed Apr 06 20:13:58 2022 +0400
# Node ID 57759a36f83b7fc23536df8108959c0c1c2818ad
# Parent  19b356eaefe883046e2d240533cfdb4096da2025
Release 13.25.0

- Fix connections duration metrics !588
- ci: start integrating go 1.18 into the CI pipelines !587
- Abort long-running unauthenticated SSH connections !582

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+v13.25.0
+
+- Fix connections duration metrics !588
+- ci: start integrating go 1.18 into the CI pipelines !587
+- Abort long-running unauthenticated SSH connections !582
+
 v13.24.2
 
 - Bump gitaly client !584
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.24.2
+13.25.0
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1649301418 0
#      Thu Apr 07 03:16:58 2022 +0000
# Node ID bdac88e392d8f4a931262f8370b693c127d1f807
# Parent  19b356eaefe883046e2d240533cfdb4096da2025
# Parent  57759a36f83b7fc23536df8108959c0c1c2818ad
Merge branch 'id-release-13.25.0' into 'main'

Release 13.25.0

See merge request gitlab-org/gitlab-shell!591

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+v13.25.0
+
+- Fix connections duration metrics !588
+- ci: start integrating go 1.18 into the CI pipelines !587
+- Abort long-running unauthenticated SSH connections !582
+
 v13.24.2
 
 - Bump gitaly client !584
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.24.2
+13.25.0
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1649309538 -36000
#      Thu Apr 07 15:32:18 2022 +1000
# Node ID a7fc1e82349c296099277708965f0ed40fe064ae
# Parent  bdac88e392d8f4a931262f8370b693c127d1f807
Upgrade golang to 1.17.8

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.7
+golang 1.17.8
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1649311195 0
#      Thu Apr 07 05:59:55 2022 +0000
# Node ID b631a53700c80225142a25d9ca0d97fc1b1d1bfe
# Parent  bdac88e392d8f4a931262f8370b693c127d1f807
# Parent  a7fc1e82349c296099277708965f0ed40fe064ae
Merge branch '554-support-using-go-version-1-17-8' into 'main'

Upgrade golang to 1.17.8

Closes #554

See merge request gitlab-org/gitlab-shell!592

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.7
+golang 1.17.8
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1649355549 -14400
#      Thu Apr 07 22:19:09 2022 +0400
# Node ID ada43a89fc2357b359022b91c972b57db67aa93f
# Parent  b631a53700c80225142a25d9ca0d97fc1b1d1bfe
Add additional metrics to gitlab-sshd

- Observe time to establish a session
- Log the duration of the successfully established connection
- Observe total time to handle the connection
- Log the duration of the successfully executed connection
- Observe the count of ssh connections
- Observe the count of failed ssh connections

diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -39,7 +39,7 @@
 	require.NoError(t, err)
 
 	var actualNames []string
-	for _, m := range ms[0:6] {
+	for _, m := range ms[0:9] {
 		actualNames = append(actualNames, m.GetName())
 	}
 
@@ -48,8 +48,11 @@
 		"gitlab_shell_http_request_duration_seconds",
 		"gitlab_shell_http_requests_total",
 		"gitlab_shell_sshd_concurrent_limited_sessions_total",
-		"gitlab_shell_sshd_connection_duration_seconds",
 		"gitlab_shell_sshd_in_flight_connections",
+		"gitlab_shell_sshd_session_duration_seconds",
+		"gitlab_shell_sshd_session_established_duration_seconds",
+		"gitlab_sli:shell_sshd_sessions:errors_total",
+		"gitlab_sli:shell_sshd_sessions:total",
 	}
 
 	require.Equal(t, expectedMetricNames, actualNames)
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -18,30 +18,42 @@
 	httpRequestsTotalMetricName          = "requests_total"
 	httpRequestDurationSecondsMetricName = "request_duration_seconds"
 
-	sshdConnectionsInFlightName = "in_flight_connections"
-	sshdConnectionDuration      = "connection_duration_seconds"
-	sshdHitMaxSessions          = "concurrent_limited_sessions_total"
+	sshdConnectionsInFlightName               = "in_flight_connections"
+	sshdHitMaxSessionsName                    = "concurrent_limited_sessions_total"
+	sshdSessionDurationSecondsName            = "session_duration_seconds"
+	sshdSessionEstablishedDurationSecondsName = "session_established_duration_seconds"
+
+	sliSshdSessionsTotalName       = "gitlab_sli:shell_sshd_sessions:total"
+	sliSshdSessionsErrorsTotalName = "gitlab_sli:shell_sshd_sessions:errors_total"
 
 	gitalyConnectionsTotalName = "connections_total"
 )
 
 var (
-	SshdConnectionDuration = promauto.NewHistogram(
+	SshdSessionDuration = promauto.NewHistogram(
 		prometheus.HistogramOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      sshdConnectionDuration,
+			Name:      sshdSessionDurationSecondsName,
 			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
 			Buckets: []float64{
-				0.005, /* 5ms */
-				0.025, /* 25ms */
-				0.1,   /* 100ms */
-				0.5,   /* 500ms */
-				1.0,   /* 1s */
-				10.0,  /* 10s */
-				30.0,  /* 30s */
-				60.0,  /* 1m */
-				300.0, /* 5m */
+				5.0,  /* 5s */
+				30.0, /* 30s */
+				60.0, /* 1m */
+			},
+		},
+	)
+
+	SshdSessionEstablishedDuration = promauto.NewHistogram(
+		prometheus.HistogramOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      sshdSessionEstablishedDurationSecondsName,
+			Help:      "A histogram of latencies until session established to gitlab-shell sshd.",
+			Buckets: []float64{
+				0.5, /* 5ms */
+				1.0, /* 1s */
+				5.0, /* 5s */
 			},
 		},
 	)
@@ -59,11 +71,25 @@
 		prometheus.CounterOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      sshdHitMaxSessions,
+			Name:      sshdHitMaxSessionsName,
 			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
 		},
 	)
 
+	SliSshdSessionsTotal = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Name: sliSshdSessionsTotalName,
+			Help: "Number of SSH sessions that have been established",
+		},
+	)
+
+	SliSshdSessionsErrorsTotal = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Name: sliSshdSessionsErrorsTotalName,
+			Help: "Number of SSH sessions that have failed",
+		},
+	)
+
 	GitalyConnectionsTotal = promauto.NewCounterVec(
 		prometheus.CounterOpts{
 			Namespace: namespace,
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -2,7 +2,6 @@
 
 import (
 	"context"
-	"time"
 
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
@@ -29,13 +28,6 @@
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
-	metrics.SshdConnectionsInFlight.Inc()
-
-	defer func(started time.Time) {
-		metrics.SshdConnectionsInFlight.Dec()
-		metrics.SshdConnectionDuration.Observe(time.Since(started).Seconds())
-	}(time.Now())
-
 	for newChannel := range chans {
 		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -22,6 +22,7 @@
 	channel     ssh.Channel
 	gitlabKeyId string
 	remoteAddr  string
+	success     bool
 
 	// State managed by the session
 	execCmd            string
@@ -182,6 +183,8 @@
 	log.WithContextFields(ctx, log.Fields{"exit_status": status}).Info("session: exit: exiting")
 	req := exitStatusReq{ExitStatus: status}
 
+	s.success = status == 0
+
 	s.channel.CloseWrite()
 	s.channel.SendRequest("exit-status", false, ssh.Marshal(req))
 }
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -99,6 +99,7 @@
 		expectedExecCmd    string
 		sentRequestName    string
 		sentRequestPayload []byte
+		success            bool
 	}{
 		{
 			desc:            "invalid payload",
@@ -111,6 +112,7 @@
 			expectedExecCmd:    "discover",
 			sentRequestName:    "exit-status",
 			sentRequestPayload: ssh.Marshal(exitStatusReq{ExitStatus: 0}),
+			success:            true,
 		},
 	}
 
@@ -130,6 +132,7 @@
 			require.Equal(t, false, s.handleExec(context.Background(), r))
 			require.Equal(t, tc.sentRequestName, f.sentRequestName)
 			require.Equal(t, tc.sentRequestPayload, f.sentRequestPayload)
+			require.Equal(t, tc.success, s.success)
 		})
 	}
 }
@@ -141,6 +144,7 @@
 		errMsg           string
 		gitlabKeyId      string
 		expectedExitCode uint32
+		success          bool
 	}{
 		{
 			desc:             "fails to parse command",
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -12,6 +12,7 @@
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/log"
@@ -145,6 +146,20 @@
 }
 
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
+	success := false
+
+	metrics.SshdConnectionsInFlight.Inc()
+	started := time.Now()
+	defer func() {
+		metrics.SshdConnectionsInFlight.Dec()
+		metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
+
+		metrics.SliSshdSessionsTotal.Inc()
+		if !success {
+			metrics.SliSshdSessionsErrorsTotal.Inc()
+		}
+	}()
+
 	remoteAddr := nconn.RemoteAddr().String()
 
 	defer s.wg.Done()
@@ -172,8 +187,12 @@
 
 	go ssh.DiscardRequests(reqs)
 
+	var establishSessionDuration float64
 	conn := newConnection(s.Config.Server.ConcurrentSessionsLimit, remoteAddr)
 	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
+		establishSessionDuration = time.Since(started).Seconds()
+		metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
+
 		session := &session{
 			cfg:         s.Config,
 			channel:     channel,
@@ -182,9 +201,14 @@
 		}
 
 		session.handle(ctx, requests)
+
+		success = session.success
 	})
 
-	ctxlog.Info("server: handleConn: done")
+	ctxlog.WithFields(log.Fields{
+		"duration_s":                   time.Since(started).Seconds(),
+		"establish_session_duration_s": establishSessionDuration,
+	}).Info("server: handleConn: done")
 }
 
 func (s *Server) initSSHConnection(ctx context.Context, nconn net.Conn) (sconn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request, err error) {
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1649915254 0
#      Thu Apr 14 05:47:34 2022 +0000
# Node ID 6d98b57cd11cdde998f1ef17fea98d4b23d96e20
# Parent  b631a53700c80225142a25d9ca0d97fc1b1d1bfe
# Parent  ada43a89fc2357b359022b91c972b57db67aa93f
Merge branch 'id-metrics-for-results' into 'main'

Add additional metrics to gitlab-sshd

See merge request gitlab-org/gitlab-shell!593

diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -39,7 +39,7 @@
 	require.NoError(t, err)
 
 	var actualNames []string
-	for _, m := range ms[0:6] {
+	for _, m := range ms[0:9] {
 		actualNames = append(actualNames, m.GetName())
 	}
 
@@ -48,8 +48,11 @@
 		"gitlab_shell_http_request_duration_seconds",
 		"gitlab_shell_http_requests_total",
 		"gitlab_shell_sshd_concurrent_limited_sessions_total",
-		"gitlab_shell_sshd_connection_duration_seconds",
 		"gitlab_shell_sshd_in_flight_connections",
+		"gitlab_shell_sshd_session_duration_seconds",
+		"gitlab_shell_sshd_session_established_duration_seconds",
+		"gitlab_sli:shell_sshd_sessions:errors_total",
+		"gitlab_sli:shell_sshd_sessions:total",
 	}
 
 	require.Equal(t, expectedMetricNames, actualNames)
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -18,30 +18,42 @@
 	httpRequestsTotalMetricName          = "requests_total"
 	httpRequestDurationSecondsMetricName = "request_duration_seconds"
 
-	sshdConnectionsInFlightName = "in_flight_connections"
-	sshdConnectionDuration      = "connection_duration_seconds"
-	sshdHitMaxSessions          = "concurrent_limited_sessions_total"
+	sshdConnectionsInFlightName               = "in_flight_connections"
+	sshdHitMaxSessionsName                    = "concurrent_limited_sessions_total"
+	sshdSessionDurationSecondsName            = "session_duration_seconds"
+	sshdSessionEstablishedDurationSecondsName = "session_established_duration_seconds"
+
+	sliSshdSessionsTotalName       = "gitlab_sli:shell_sshd_sessions:total"
+	sliSshdSessionsErrorsTotalName = "gitlab_sli:shell_sshd_sessions:errors_total"
 
 	gitalyConnectionsTotalName = "connections_total"
 )
 
 var (
-	SshdConnectionDuration = promauto.NewHistogram(
+	SshdSessionDuration = promauto.NewHistogram(
 		prometheus.HistogramOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      sshdConnectionDuration,
+			Name:      sshdSessionDurationSecondsName,
 			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
 			Buckets: []float64{
-				0.005, /* 5ms */
-				0.025, /* 25ms */
-				0.1,   /* 100ms */
-				0.5,   /* 500ms */
-				1.0,   /* 1s */
-				10.0,  /* 10s */
-				30.0,  /* 30s */
-				60.0,  /* 1m */
-				300.0, /* 5m */
+				5.0,  /* 5s */
+				30.0, /* 30s */
+				60.0, /* 1m */
+			},
+		},
+	)
+
+	SshdSessionEstablishedDuration = promauto.NewHistogram(
+		prometheus.HistogramOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      sshdSessionEstablishedDurationSecondsName,
+			Help:      "A histogram of latencies until session established to gitlab-shell sshd.",
+			Buckets: []float64{
+				0.5, /* 5ms */
+				1.0, /* 1s */
+				5.0, /* 5s */
 			},
 		},
 	)
@@ -59,11 +71,25 @@
 		prometheus.CounterOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      sshdHitMaxSessions,
+			Name:      sshdHitMaxSessionsName,
 			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
 		},
 	)
 
+	SliSshdSessionsTotal = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Name: sliSshdSessionsTotalName,
+			Help: "Number of SSH sessions that have been established",
+		},
+	)
+
+	SliSshdSessionsErrorsTotal = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Name: sliSshdSessionsErrorsTotalName,
+			Help: "Number of SSH sessions that have failed",
+		},
+	)
+
 	GitalyConnectionsTotal = promauto.NewCounterVec(
 		prometheus.CounterOpts{
 			Namespace: namespace,
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -2,7 +2,6 @@
 
 import (
 	"context"
-	"time"
 
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
@@ -29,13 +28,6 @@
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
-	metrics.SshdConnectionsInFlight.Inc()
-
-	defer func(started time.Time) {
-		metrics.SshdConnectionsInFlight.Dec()
-		metrics.SshdConnectionDuration.Observe(time.Since(started).Seconds())
-	}(time.Now())
-
 	for newChannel := range chans {
 		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -22,6 +22,7 @@
 	channel     ssh.Channel
 	gitlabKeyId string
 	remoteAddr  string
+	success     bool
 
 	// State managed by the session
 	execCmd            string
@@ -182,6 +183,8 @@
 	log.WithContextFields(ctx, log.Fields{"exit_status": status}).Info("session: exit: exiting")
 	req := exitStatusReq{ExitStatus: status}
 
+	s.success = status == 0
+
 	s.channel.CloseWrite()
 	s.channel.SendRequest("exit-status", false, ssh.Marshal(req))
 }
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -99,6 +99,7 @@
 		expectedExecCmd    string
 		sentRequestName    string
 		sentRequestPayload []byte
+		success            bool
 	}{
 		{
 			desc:            "invalid payload",
@@ -111,6 +112,7 @@
 			expectedExecCmd:    "discover",
 			sentRequestName:    "exit-status",
 			sentRequestPayload: ssh.Marshal(exitStatusReq{ExitStatus: 0}),
+			success:            true,
 		},
 	}
 
@@ -130,6 +132,7 @@
 			require.Equal(t, false, s.handleExec(context.Background(), r))
 			require.Equal(t, tc.sentRequestName, f.sentRequestName)
 			require.Equal(t, tc.sentRequestPayload, f.sentRequestPayload)
+			require.Equal(t, tc.success, s.success)
 		})
 	}
 }
@@ -141,6 +144,7 @@
 		errMsg           string
 		gitlabKeyId      string
 		expectedExitCode uint32
+		success          bool
 	}{
 		{
 			desc:             "fails to parse command",
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -12,6 +12,7 @@
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/log"
@@ -145,6 +146,20 @@
 }
 
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
+	success := false
+
+	metrics.SshdConnectionsInFlight.Inc()
+	started := time.Now()
+	defer func() {
+		metrics.SshdConnectionsInFlight.Dec()
+		metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
+
+		metrics.SliSshdSessionsTotal.Inc()
+		if !success {
+			metrics.SliSshdSessionsErrorsTotal.Inc()
+		}
+	}()
+
 	remoteAddr := nconn.RemoteAddr().String()
 
 	defer s.wg.Done()
@@ -172,8 +187,12 @@
 
 	go ssh.DiscardRequests(reqs)
 
+	var establishSessionDuration float64
 	conn := newConnection(s.Config.Server.ConcurrentSessionsLimit, remoteAddr)
 	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
+		establishSessionDuration = time.Since(started).Seconds()
+		metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
+
 		session := &session{
 			cfg:         s.Config,
 			channel:     channel,
@@ -182,9 +201,14 @@
 		}
 
 		session.handle(ctx, requests)
+
+		success = session.success
 	})
 
-	ctxlog.Info("server: handleConn: done")
+	ctxlog.WithFields(log.Fields{
+		"duration_s":                   time.Since(started).Seconds(),
+		"establish_session_duration_s": establishSessionDuration,
+	}).Info("server: handleConn: done")
 }
 
 func (s *Server) initSSHConnection(ctx context.Context, nconn net.Conn) (sconn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request, err error) {
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1650309091 25200
#      Mon Apr 18 12:11:31 2022 -0700
# Node ID 64fb809c76dfdb5350e57d1576eacf76121b6b48
# Parent  6d98b57cd11cdde998f1ef17fea98d4b23d96e20
Add support for FIPS encryption

This commit adds support of using a FIPS-validated SSL library with
compiled Go executables when `FIPS_MODE=1 make` is run. A Go compiler
that supports BoringSSL either directly (e.g. the `dev.boringcrypto`
branch) or with a dynamically linked OpenSSL
(e.g. https://github.com/golang-fips/go) is required.

This is similar to the changes to support FIPS in GitLab Runner and in
GitLab Pages:
https://gitlab.com/gitlab-org/gitlab-pages/-/merge_requests/716

Changelog: added

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,15 @@
 .PHONY: validate verify verify_ruby verify_golang test test_ruby test_golang coverage coverage_golang setup _script_install build compile check clean install
 
+FIPS_MODE ?= 0
 GO_SOURCES := $(shell find . -name '*.go')
 VERSION_STRING := $(shell git describe --match v* 2>/dev/null || awk '$$0="v"$$0' VERSION 2>/dev/null || echo unknown)
 BUILD_TIME := $(shell date -u +%Y%m%d.%H%M%S)
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
+
+ifeq (${FIPS_MODE}, 1)
+    BUILD_TAGS += boringcrypto
+endif
+
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)" -mod=mod
 
 PREFIX ?= /usr/local
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -11,6 +11,7 @@
 	"gitlab.com/gitlab-org/labkit/log"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/boring"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -73,6 +74,7 @@
 	cmdName := reflect.TypeOf(cmd).String()
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("gitlab-shell: main: executing command")
+	boring.CheckBoring()
 
 	if err := cmd.Execute(ctx); err != nil {
 		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
diff --git a/internal/boring/boring.go b/internal/boring/boring.go
new file mode 100644
--- /dev/null
+++ b/internal/boring/boring.go
@@ -0,0 +1,23 @@
+//go:build boringcrypto
+// +build boringcrypto
+
+package boring
+
+import (
+	"crypto/boring"
+
+	"gitlab.com/gitlab-org/labkit/log"
+)
+
+// CheckBoring checks whether FIPS crypto has been enabled. For the FIPS Go
+// compiler in https://github.com/golang-fips/go, this requires that:
+//
+// 1. The kernel has FIPS enabled (e.g. `/proc/sys/crypto/fips_enabled` is 1).
+// 2. A system OpenSSL can be dynamically loaded via ldopen().
+func CheckBoring() {
+	if boring.Enabled() {
+		log.Info("FIPS mode is enabled. Using an external SSL library.")
+		return
+	}
+	log.Info("Gitaly was compiled with FIPS mode, but an external SSL library was not enabled.")
+}
diff --git a/internal/boring/notboring.go b/internal/boring/notboring.go
new file mode 100644
--- /dev/null
+++ b/internal/boring/notboring.go
@@ -0,0 +1,9 @@
+//go:build !boringcrypto
+// +build !boringcrypto
+
+package boring
+
+// CheckBoring does nothing when the boringcrypto tag is not in the
+// build.
+func CheckBoring() {
+}
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1650368543 0
#      Tue Apr 19 11:42:23 2022 +0000
# Node ID 66fce7a79fc2e0b2e5f7482aeeb0df6754fd4586
# Parent  6d98b57cd11cdde998f1ef17fea98d4b23d96e20
# Parent  64fb809c76dfdb5350e57d1576eacf76121b6b48
Merge branch 'sh-fips-mode' into 'main'

Add support for FIPS encryption

See merge request gitlab-org/gitlab-shell!597

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,15 @@
 .PHONY: validate verify verify_ruby verify_golang test test_ruby test_golang coverage coverage_golang setup _script_install build compile check clean install
 
+FIPS_MODE ?= 0
 GO_SOURCES := $(shell find . -name '*.go')
 VERSION_STRING := $(shell git describe --match v* 2>/dev/null || awk '$$0="v"$$0' VERSION 2>/dev/null || echo unknown)
 BUILD_TIME := $(shell date -u +%Y%m%d.%H%M%S)
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
+
+ifeq (${FIPS_MODE}, 1)
+    BUILD_TAGS += boringcrypto
+endif
+
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)" -mod=mod
 
 PREFIX ?= /usr/local
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -11,6 +11,7 @@
 	"gitlab.com/gitlab-org/labkit/log"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/boring"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -73,6 +74,7 @@
 	cmdName := reflect.TypeOf(cmd).String()
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("gitlab-shell: main: executing command")
+	boring.CheckBoring()
 
 	if err := cmd.Execute(ctx); err != nil {
 		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
diff --git a/internal/boring/boring.go b/internal/boring/boring.go
new file mode 100644
--- /dev/null
+++ b/internal/boring/boring.go
@@ -0,0 +1,23 @@
+//go:build boringcrypto
+// +build boringcrypto
+
+package boring
+
+import (
+	"crypto/boring"
+
+	"gitlab.com/gitlab-org/labkit/log"
+)
+
+// CheckBoring checks whether FIPS crypto has been enabled. For the FIPS Go
+// compiler in https://github.com/golang-fips/go, this requires that:
+//
+// 1. The kernel has FIPS enabled (e.g. `/proc/sys/crypto/fips_enabled` is 1).
+// 2. A system OpenSSL can be dynamically loaded via ldopen().
+func CheckBoring() {
+	if boring.Enabled() {
+		log.Info("FIPS mode is enabled. Using an external SSL library.")
+		return
+	}
+	log.Info("Gitaly was compiled with FIPS mode, but an external SSL library was not enabled.")
+}
diff --git a/internal/boring/notboring.go b/internal/boring/notboring.go
new file mode 100644
--- /dev/null
+++ b/internal/boring/notboring.go
@@ -0,0 +1,9 @@
+//go:build !boringcrypto
+// +build !boringcrypto
+
+package boring
+
+// CheckBoring does nothing when the boringcrypto tag is not in the
+// build.
+func CheckBoring() {
+}
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1650368868 -14400
#      Tue Apr 19 15:47:48 2022 +0400
# Node ID 13c1bfd3c5cdbdc0ad5bdc3e7a6668f01370c5c1
# Parent  66fce7a79fc2e0b2e5f7482aeeb0df6754fd4586
Release 13.25.1

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+v13.25.1
+
+- Upgrade golang to 1.17.8 !591
+- Add additional metrics to gitlab-sshd !593
+- Add support for FIPS encryption !597
+
 v13.25.0
 
 - Fix connections duration metrics !588
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.25.0
+13.25.1
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1650369339 0
#      Tue Apr 19 11:55:39 2022 +0000
# Node ID 6712c68c42156ec09eefaab72446f498341d0464
# Parent  66fce7a79fc2e0b2e5f7482aeeb0df6754fd4586
# Parent  13c1bfd3c5cdbdc0ad5bdc3e7a6668f01370c5c1
Merge branch 'id-release-13-25-1' into 'main'

Release 13.25.1

See merge request gitlab-org/gitlab-shell!598

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+v13.25.1
+
+- Upgrade golang to 1.17.8 !591
+- Add additional metrics to gitlab-sshd !593
+- Add support for FIPS encryption !597
+
 v13.25.0
 
 - Fix connections duration metrics !588
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.25.0
+13.25.1
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1650377784 25200
#      Tue Apr 19 07:16:24 2022 -0700
# Node ID a840f4d80bd90e7c69d7e6e4556f468a63063c63
# Parent  6712c68c42156ec09eefaab72446f498341d0464
Fix typo in FIPS mode message

Rename Gitaly -> gitlab-shell

diff --git a/internal/boring/boring.go b/internal/boring/boring.go
--- a/internal/boring/boring.go
+++ b/internal/boring/boring.go
@@ -19,5 +19,5 @@
 		log.Info("FIPS mode is enabled. Using an external SSL library.")
 		return
 	}
-	log.Info("Gitaly was compiled with FIPS mode, but an external SSL library was not enabled.")
+	log.Info("gitlab-shell was compiled with FIPS mode, but an external SSL library was not enabled.")
 }
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1650523534 0
#      Thu Apr 21 06:45:34 2022 +0000
# Node ID 6a516f324c6ed4a62995ebccb54d967c85b0b8c0
# Parent  6712c68c42156ec09eefaab72446f498341d0464
# Parent  a840f4d80bd90e7c69d7e6e4556f468a63063c63
Merge branch 'sh-fix-typo-fips' into 'main'

Fix typo in FIPS mode message

See merge request gitlab-org/gitlab-shell!599

diff --git a/internal/boring/boring.go b/internal/boring/boring.go
--- a/internal/boring/boring.go
+++ b/internal/boring/boring.go
@@ -19,5 +19,5 @@
 		log.Info("FIPS mode is enabled. Using an external SSL library.")
 		return
 	}
-	log.Info("Gitaly was compiled with FIPS mode, but an external SSL library was not enabled.")
+	log.Info("gitlab-shell was compiled with FIPS mode, but an external SSL library was not enabled.")
 }
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1650483924 -7200
#      Wed Apr 20 21:45:24 2022 +0200
# Node ID 7bdc7bddd3c8ceb182e43af090c35dc8a4b0eb64
# Parent  6712c68c42156ec09eefaab72446f498341d0464
Bump Go to 1.17.9 for asdf users

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.8
+golang 1.17.9
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1650525150 0
#      Thu Apr 21 07:12:30 2022 +0000
# Node ID 5c5ea97a01d86c20b2843fa1b81d54a287a37fad
# Parent  6a516f324c6ed4a62995ebccb54d967c85b0b8c0
# Parent  7bdc7bddd3c8ceb182e43af090c35dc8a4b0eb64
Merge branch 'bump/go-tool-version' into 'main'

Bump Go to 1.17.9 for asdf users

See merge request gitlab-org/gitlab-shell!600

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.8
+golang 1.17.9
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1650889855 -14400
#      Mon Apr 25 16:30:55 2022 +0400
# Node ID c78f2ea27d8184ce358ae0710d6b78c71dd4c34e
# Parent  5c5ea97a01d86c20b2843fa1b81d54a287a37fad
Revert "Abort long-running unauthenticated SSH connections"

This reverts commit 3a2c8f2c47774a35d840ec8baf54341beede5d43.

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -84,8 +84,6 @@
   readiness_probe: "/start"
   # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
   liveness_probe: "/health"
-  # The server disconnects after this time (in seconds) if the user has not successfully logged in. Defaults to 60.
-  login_grace_time: 60
   # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -26,7 +26,6 @@
 	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
 	WebListen               string   `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
-	LoginGraceTimeSeconds   uint64   `yaml:"login_grace_time"`
 	GracePeriodSeconds      uint64   `yaml:"grace_period"`
 	ReadinessProbe          string   `yaml:"readiness_probe"`
 	LivenessProbe           string   `yaml:"liveness_probe"`
@@ -79,7 +78,6 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
-		LoginGraceTimeSeconds:   60,
 		GracePeriodSeconds:      10,
 		ReadinessProbe:          "/start",
 		LivenessProbe:           "/health",
@@ -91,10 +89,6 @@
 	}
 )
 
-func (sc *ServerConfig) LoginGraceTime() time.Duration {
-	return time.Duration(sc.LoginGraceTimeSeconds) * time.Second
-}
-
 func (sc *ServerConfig) GracePeriod() time.Duration {
 	return time.Duration(sc.GracePeriodSeconds) * time.Second
 }
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -179,12 +179,11 @@
 
 	ctxlog.Info("server: handleConn: start")
 
-	sconn, chans, reqs, err := s.initSSHConnection(ctx, nconn)
+	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
 		ctxlog.WithError(err).Error("server: handleConn: failed to initialize SSH connection")
 		return
 	}
-
 	go ssh.DiscardRequests(reqs)
 
 	var establishSessionDuration float64
@@ -211,20 +210,6 @@
 	}).Info("server: handleConn: done")
 }
 
-func (s *Server) initSSHConnection(ctx context.Context, nconn net.Conn) (sconn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request, err error) {
-	timer := time.AfterFunc(s.Config.Server.LoginGraceTime(), func() {
-		nconn.Close()
-	})
-	defer func() {
-		// If time.Stop() equals false, that means that AfterFunc has been executed
-		if !timer.Stop() {
-			err = fmt.Errorf("initSSHConnection: ssh handshake timeout of %v", s.Config.Server.LoginGraceTime())
-		}
-	}()
-
-	return ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
-}
-
 func unconditionalRequirePolicy(_ net.Addr) (proxyproto.Policy, error) {
 	return proxyproto.REQUIRE, nil
 }
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -3,7 +3,6 @@
 import (
 	"context"
 	"fmt"
-	"net"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -135,33 +134,6 @@
 	require.Nil(t, s.Shutdown())
 }
 
-func TestLoginGraceTime(t *testing.T) {
-	s := setupServer(t)
-
-	unauthenticatedRequestStatus := make(chan string)
-	completed := make(chan bool)
-
-	clientCfg := clientConfig(t)
-	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
-		unauthenticatedRequestStatus <- "authentication-started"
-		<-completed // Wait infinitely
-
-		return nil
-	}
-
-	go func() {
-		// Start an SSH connection that never ends
-		ssh.Dial("tcp", serverUrl, clientCfg)
-	}()
-
-	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
-
-	// Verify that the server shutdowns instantly without waiting for any connection to execute
-	// That means that the server doesn't have any connections to wait for
-	require.NoError(t, s.Shutdown())
-	verifyStatus(t, s, StatusClosed)
-}
-
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
@@ -211,7 +183,6 @@
 	cfg.User = user
 	cfg.Server.Listen = serverUrl
 	cfg.Server.ConcurrentSessionsLimit = 1
-	cfg.Server.LoginGraceTimeSeconds = 1
 	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
 
 	s, err := NewServer(cfg)
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1650943475 0
#      Tue Apr 26 03:24:35 2022 +0000
# Node ID 74166f5062a6becbc92fb7401b1e0ffaef7ac8cd
# Parent  5c5ea97a01d86c20b2843fa1b81d54a287a37fad
# Parent  c78f2ea27d8184ce358ae0710d6b78c71dd4c34e
Merge branch 'id-revert-ssh-connection-timeouts' into 'main'

Revert "Abort long-running unauthenticated SSH connections"

See merge request gitlab-org/gitlab-shell!605

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -84,8 +84,6 @@
   readiness_probe: "/start"
   # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
   liveness_probe: "/health"
-  # The server disconnects after this time (in seconds) if the user has not successfully logged in. Defaults to 60.
-  login_grace_time: 60
   # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -26,7 +26,6 @@
 	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
 	WebListen               string   `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
-	LoginGraceTimeSeconds   uint64   `yaml:"login_grace_time"`
 	GracePeriodSeconds      uint64   `yaml:"grace_period"`
 	ReadinessProbe          string   `yaml:"readiness_probe"`
 	LivenessProbe           string   `yaml:"liveness_probe"`
@@ -79,7 +78,6 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
-		LoginGraceTimeSeconds:   60,
 		GracePeriodSeconds:      10,
 		ReadinessProbe:          "/start",
 		LivenessProbe:           "/health",
@@ -91,10 +89,6 @@
 	}
 )
 
-func (sc *ServerConfig) LoginGraceTime() time.Duration {
-	return time.Duration(sc.LoginGraceTimeSeconds) * time.Second
-}
-
 func (sc *ServerConfig) GracePeriod() time.Duration {
 	return time.Duration(sc.GracePeriodSeconds) * time.Second
 }
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -179,12 +179,11 @@
 
 	ctxlog.Info("server: handleConn: start")
 
-	sconn, chans, reqs, err := s.initSSHConnection(ctx, nconn)
+	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
 		ctxlog.WithError(err).Error("server: handleConn: failed to initialize SSH connection")
 		return
 	}
-
 	go ssh.DiscardRequests(reqs)
 
 	var establishSessionDuration float64
@@ -211,20 +210,6 @@
 	}).Info("server: handleConn: done")
 }
 
-func (s *Server) initSSHConnection(ctx context.Context, nconn net.Conn) (sconn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request, err error) {
-	timer := time.AfterFunc(s.Config.Server.LoginGraceTime(), func() {
-		nconn.Close()
-	})
-	defer func() {
-		// If time.Stop() equals false, that means that AfterFunc has been executed
-		if !timer.Stop() {
-			err = fmt.Errorf("initSSHConnection: ssh handshake timeout of %v", s.Config.Server.LoginGraceTime())
-		}
-	}()
-
-	return ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
-}
-
 func unconditionalRequirePolicy(_ net.Addr) (proxyproto.Policy, error) {
 	return proxyproto.REQUIRE, nil
 }
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -3,7 +3,6 @@
 import (
 	"context"
 	"fmt"
-	"net"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -135,33 +134,6 @@
 	require.Nil(t, s.Shutdown())
 }
 
-func TestLoginGraceTime(t *testing.T) {
-	s := setupServer(t)
-
-	unauthenticatedRequestStatus := make(chan string)
-	completed := make(chan bool)
-
-	clientCfg := clientConfig(t)
-	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
-		unauthenticatedRequestStatus <- "authentication-started"
-		<-completed // Wait infinitely
-
-		return nil
-	}
-
-	go func() {
-		// Start an SSH connection that never ends
-		ssh.Dial("tcp", serverUrl, clientCfg)
-	}()
-
-	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
-
-	// Verify that the server shutdowns instantly without waiting for any connection to execute
-	// That means that the server doesn't have any connections to wait for
-	require.NoError(t, s.Shutdown())
-	verifyStatus(t, s, StatusClosed)
-}
-
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
@@ -211,7 +183,6 @@
 	cfg.User = user
 	cfg.Server.Listen = serverUrl
 	cfg.Server.ConcurrentSessionsLimit = 1
-	cfg.Server.LoginGraceTimeSeconds = 1
 	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
 
 	s, err := NewServer(cfg)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1650943629 -14400
#      Tue Apr 26 07:27:09 2022 +0400
# Node ID c7ed2cccab7d03a92e70f77bb4eaecfe0d832707
# Parent  74166f5062a6becbc92fb7401b1e0ffaef7ac8cd
Release v13.25.2

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v13.25.2
+
+- Revert "Abort long-running unauthenticated SSH connections" !605
+- Bump Go to 1.17.9 for asdf users !600
+
 v13.25.1
 
 - Upgrade golang to 1.17.8 !591
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.25.1
+13.25.2
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1650944082 0
#      Tue Apr 26 03:34:42 2022 +0000
# Node ID 3ab9ac91927af7dec6116509b5614a0d23048673
# Parent  74166f5062a6becbc92fb7401b1e0ffaef7ac8cd
# Parent  c7ed2cccab7d03a92e70f77bb4eaecfe0d832707
Merge branch 'id-release-13-25-2' into 'main'

Release v13.25.2

See merge request gitlab-org/gitlab-shell!606

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v13.25.2
+
+- Revert "Abort long-running unauthenticated SSH connections" !605
+- Bump Go to 1.17.9 for asdf users !600
+
 v13.25.1
 
 - Upgrade golang to 1.17.8 !591
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.25.1
+13.25.2
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1650661577 -7200
#      Fri Apr 22 23:06:17 2022 +0200
# Node ID 48fc1074c7d11801c7fe3dd57d9ebaa90bd0e354
# Parent  5c5ea97a01d86c20b2843fa1b81d54a287a37fad
feat: replace status mutex with RWMutex

diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -32,7 +32,7 @@
 	Config *config.Config
 
 	status       status
-	statusMu     sync.Mutex
+	statusMu     sync.RWMutex
 	wg           sync.WaitGroup
 	listener     net.Listener
 	serverConfig *serverConfig
@@ -139,8 +139,8 @@
 }
 
 func (s *Server) getStatus() status {
-	s.statusMu.Lock()
-	defer s.statusMu.Unlock()
+	s.statusMu.RLock()
+	defer s.statusMu.RUnlock()
 
 	return s.status
 }
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1650949884 0
#      Tue Apr 26 05:11:24 2022 +0000
# Node ID b2afbed301d648dec2caa9efc52027cb72a2e0e4
# Parent  3ab9ac91927af7dec6116509b5614a0d23048673
# Parent  48fc1074c7d11801c7fe3dd57d9ebaa90bd0e354
Merge branch 'feat/status-rwmutex' into 'main'

feat: replace status mutex with RWMutex

See merge request gitlab-org/gitlab-shell!604

diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -32,7 +32,7 @@
 	Config *config.Config
 
 	status       status
-	statusMu     sync.Mutex
+	statusMu     sync.RWMutex
 	wg           sync.WaitGroup
 	listener     net.Listener
 	serverConfig *serverConfig
@@ -139,8 +139,8 @@
 }
 
 func (s *Server) getStatus() status {
-	s.statusMu.Lock()
-	defer s.statusMu.Unlock()
+	s.statusMu.RLock()
+	defer s.statusMu.RUnlock()
 
 	return s.status
 }
# HG changeset patch
# User Vasilii Iakliushin <viakliushin@gitlab.com>
# Date 1650626839 -7200
#      Fri Apr 22 13:27:19 2022 +0200
# Node ID 25cf56ca13b111ce13fcc2d0b57cbf73e826787e
# Parent  5c5ea97a01d86c20b2843fa1b81d54a287a37fad
Remove `self_signed_cert` option

Contributes to https://gitlab.com/gitlab-org/gitlab-shell/-/issues/541

Changelog: removed

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -59,7 +59,7 @@
 
 			secret := "sssh, it's a secret"
 
-			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", false, 1, nil)
+			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", 1, nil)
 			require.NoError(t, err)
 
 			client, err := NewGitlabNetClient("", "", secret, httpClient)
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -71,8 +71,8 @@
 }
 
 // Deprecated: use NewHTTPClientWithOpts - https://gitlab.com/gitlab-org/gitlab-shell/-/issues/484
-func NewHTTPClient(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64) *HttpClient {
-	c, err := NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath, selfSignedCert, readTimeoutSeconds, nil)
+func NewHTTPClient(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, readTimeoutSeconds uint64) *HttpClient {
+	c, err := NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath, readTimeoutSeconds, nil)
 	if err != nil {
 		log.WithError(err).Error("new http client with opts")
 	}
@@ -80,7 +80,7 @@
 }
 
 // NewHTTPClientWithOpts builds an HTTP client using the provided options
-func NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64, opts []HTTPClientOpt) (*HttpClient, error) {
+func NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, readTimeoutSeconds uint64, opts []HTTPClientOpt) (*HttpClient, error) {
 	var transport *http.Transport
 	var host string
 	var err error
@@ -103,7 +103,7 @@
 			opt(hcc)
 		}
 
-		transport, host, err = buildHttpsTransport(*hcc, selfSignedCert, gitlabURL)
+		transport, host, err = buildHttpsTransport(*hcc, gitlabURL)
 		if err != nil {
 			return nil, err
 		}
@@ -140,7 +140,7 @@
 	return transport, host
 }
 
-func buildHttpsTransport(hcc httpClientCfg, selfSignedCert bool, gitlabURL string) (*http.Transport, string, error) {
+func buildHttpsTransport(hcc httpClientCfg, gitlabURL string) (*http.Transport, string, error) {
 	certPool, err := x509.SystemCertPool()
 
 	if err != nil {
@@ -162,12 +162,8 @@
 		}
 	}
 	tlsConfig := &tls.Config{
-		RootCAs: certPool,
-		// The self_signed_cert config setting is deprecated
-		// The field and its usage is going to be removed in
-		// https://gitlab.com/gitlab-org/gitlab-shell/-/issues/541
-		InsecureSkipVerify: selfSignedCert,
-		MinVersion:         tls.VersionTLS12,
+		RootCAs:    certPool,
+		MinVersion: tls.VersionTLS12,
 	}
 
 	if hcc.HaveCertAndKey() {
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -17,7 +17,7 @@
 func TestReadTimeout(t *testing.T) {
 	expectedSeconds := uint64(300)
 
-	client, err := NewHTTPClientWithOpts("http://localhost:3000", "", "", "", false, expectedSeconds, nil)
+	client, err := NewHTTPClientWithOpts("http://localhost:3000", "", "", "", expectedSeconds, nil)
 	require.NoError(t, err)
 
 	require.NotNil(t, client)
@@ -123,7 +123,7 @@
 func setup(t *testing.T, username, password string, requests []testserver.TestRequestHandler) *GitlabNetClient {
 	url := testserver.StartHttpServer(t, requests)
 
-	httpClient, err := NewHTTPClientWithOpts(url, "", "", "", false, 1, nil)
+	httpClient, err := NewHTTPClientWithOpts(url, "", "", "", 1, nil)
 	require.NoError(t, err)
 
 	client, err := NewGitlabNetClient(username, password, "", httpClient)
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -18,7 +18,6 @@
 	testCases := []struct {
 		desc                                        string
 		caFile, caPath                              string
-		selfSigned                                  bool
 		clientCAPath, clientCertPath, clientKeyPath string // used for TLS client certs
 	}{
 		{
@@ -31,9 +30,8 @@
 			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
 		},
 		{
-			desc:       "Invalid cert with self signed cert option enabled",
-			caFile:     path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
-			selfSigned: true,
+			desc:   "Invalid cert with self signed cert option enabled",
+			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
 		},
 		{
 			desc:   "Client certs with CA",
@@ -48,7 +46,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client, err := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath, tc.selfSigned)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath)
 			require.NoError(t, err)
 
 			response, err := client.Get(context.Background(), "/hello")
@@ -95,7 +93,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client, err := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "", false)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "")
 			if tc.expectedCaFileNotFound {
 				require.Error(t, err)
 				require.ErrorIs(t, err, ErrCafileNotFound)
@@ -109,7 +107,7 @@
 	}
 }
 
-func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string, selfSigned bool) (*GitlabNetClient, error) {
+func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string) (*GitlabNetClient, error) {
 	testhelper.PrepareTestRootDir(t)
 
 	requests := []testserver.TestRequestHandler{
@@ -130,7 +128,7 @@
 		opts = append(opts, WithClientCert(clientCertPath, clientKeyPath))
 	}
 
-	httpClient, err := NewHTTPClientWithOpts(url, "", caFile, caPath, selfSigned, 1, opts)
+	httpClient, err := NewHTTPClientWithOpts(url, "", caFile, caPath, 1, opts)
 	if err != nil {
 		return nil, err
 	}
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -27,11 +27,6 @@
 #  ca_file: /etc/ssl/cert.pem
 #  ca_path: /etc/pki/tls/certs
 #
-#  The self_signed_cert option is deprecated
-#  When it's set to true, any certificate is accepted, which may make machine-in-the-middle attack possible
-#  Certificates specified in ca_file and ca_path are trusted anyway even if they are self-signed
-#  Issue: https://gitlab.com/gitlab-org/gitlab-shell/-/issues/120
-  self_signed_cert: false
 
 # File used as authorized_keys for gitlab user
 auth_file: "/home/git/.ssh/authorized_keys"
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -39,7 +39,6 @@
 	ReadTimeoutSeconds uint64 `yaml:"read_timeout"`
 	CaFile             string `yaml:"ca_file"`
 	CaPath             string `yaml:"ca_path"`
-	SelfSignedCert     bool   `yaml:"self_signed_cert"`
 }
 
 type Config struct {
@@ -112,7 +111,6 @@
 			c.GitlabRelativeURLRoot,
 			c.HttpSettings.CaFile,
 			c.HttpSettings.CaPath,
-			c.HttpSettings.SelfSignedCert,
 			c.HttpSettings.ReadTimeoutSeconds,
 			nil,
 		)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1650949949 0
#      Tue Apr 26 05:12:29 2022 +0000
# Node ID 14d2e1cc1ddc0a2859d2100e599aac602b19f2ed
# Parent  b2afbed301d648dec2caa9efc52027cb72a2e0e4
# Parent  25cf56ca13b111ce13fcc2d0b57cbf73e826787e
Merge branch '541_remove_self_signed_cert_option' into 'main'

Remove `self_signed_cert` option

See merge request gitlab-org/gitlab-shell!602

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -59,7 +59,7 @@
 
 			secret := "sssh, it's a secret"
 
-			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", false, 1, nil)
+			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", 1, nil)
 			require.NoError(t, err)
 
 			client, err := NewGitlabNetClient("", "", secret, httpClient)
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -71,8 +71,8 @@
 }
 
 // Deprecated: use NewHTTPClientWithOpts - https://gitlab.com/gitlab-org/gitlab-shell/-/issues/484
-func NewHTTPClient(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64) *HttpClient {
-	c, err := NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath, selfSignedCert, readTimeoutSeconds, nil)
+func NewHTTPClient(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, readTimeoutSeconds uint64) *HttpClient {
+	c, err := NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath, readTimeoutSeconds, nil)
 	if err != nil {
 		log.WithError(err).Error("new http client with opts")
 	}
@@ -80,7 +80,7 @@
 }
 
 // NewHTTPClientWithOpts builds an HTTP client using the provided options
-func NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64, opts []HTTPClientOpt) (*HttpClient, error) {
+func NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, readTimeoutSeconds uint64, opts []HTTPClientOpt) (*HttpClient, error) {
 	var transport *http.Transport
 	var host string
 	var err error
@@ -103,7 +103,7 @@
 			opt(hcc)
 		}
 
-		transport, host, err = buildHttpsTransport(*hcc, selfSignedCert, gitlabURL)
+		transport, host, err = buildHttpsTransport(*hcc, gitlabURL)
 		if err != nil {
 			return nil, err
 		}
@@ -140,7 +140,7 @@
 	return transport, host
 }
 
-func buildHttpsTransport(hcc httpClientCfg, selfSignedCert bool, gitlabURL string) (*http.Transport, string, error) {
+func buildHttpsTransport(hcc httpClientCfg, gitlabURL string) (*http.Transport, string, error) {
 	certPool, err := x509.SystemCertPool()
 
 	if err != nil {
@@ -162,12 +162,8 @@
 		}
 	}
 	tlsConfig := &tls.Config{
-		RootCAs: certPool,
-		// The self_signed_cert config setting is deprecated
-		// The field and its usage is going to be removed in
-		// https://gitlab.com/gitlab-org/gitlab-shell/-/issues/541
-		InsecureSkipVerify: selfSignedCert,
-		MinVersion:         tls.VersionTLS12,
+		RootCAs:    certPool,
+		MinVersion: tls.VersionTLS12,
 	}
 
 	if hcc.HaveCertAndKey() {
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -17,7 +17,7 @@
 func TestReadTimeout(t *testing.T) {
 	expectedSeconds := uint64(300)
 
-	client, err := NewHTTPClientWithOpts("http://localhost:3000", "", "", "", false, expectedSeconds, nil)
+	client, err := NewHTTPClientWithOpts("http://localhost:3000", "", "", "", expectedSeconds, nil)
 	require.NoError(t, err)
 
 	require.NotNil(t, client)
@@ -123,7 +123,7 @@
 func setup(t *testing.T, username, password string, requests []testserver.TestRequestHandler) *GitlabNetClient {
 	url := testserver.StartHttpServer(t, requests)
 
-	httpClient, err := NewHTTPClientWithOpts(url, "", "", "", false, 1, nil)
+	httpClient, err := NewHTTPClientWithOpts(url, "", "", "", 1, nil)
 	require.NoError(t, err)
 
 	client, err := NewGitlabNetClient(username, password, "", httpClient)
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -18,7 +18,6 @@
 	testCases := []struct {
 		desc                                        string
 		caFile, caPath                              string
-		selfSigned                                  bool
 		clientCAPath, clientCertPath, clientKeyPath string // used for TLS client certs
 	}{
 		{
@@ -31,9 +30,8 @@
 			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
 		},
 		{
-			desc:       "Invalid cert with self signed cert option enabled",
-			caFile:     path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
-			selfSigned: true,
+			desc:   "Invalid cert with self signed cert option enabled",
+			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
 		},
 		{
 			desc:   "Client certs with CA",
@@ -48,7 +46,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client, err := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath, tc.selfSigned)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath)
 			require.NoError(t, err)
 
 			response, err := client.Get(context.Background(), "/hello")
@@ -95,7 +93,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client, err := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "", false)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "")
 			if tc.expectedCaFileNotFound {
 				require.Error(t, err)
 				require.ErrorIs(t, err, ErrCafileNotFound)
@@ -109,7 +107,7 @@
 	}
 }
 
-func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string, selfSigned bool) (*GitlabNetClient, error) {
+func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string) (*GitlabNetClient, error) {
 	testhelper.PrepareTestRootDir(t)
 
 	requests := []testserver.TestRequestHandler{
@@ -130,7 +128,7 @@
 		opts = append(opts, WithClientCert(clientCertPath, clientKeyPath))
 	}
 
-	httpClient, err := NewHTTPClientWithOpts(url, "", caFile, caPath, selfSigned, 1, opts)
+	httpClient, err := NewHTTPClientWithOpts(url, "", caFile, caPath, 1, opts)
 	if err != nil {
 		return nil, err
 	}
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -27,11 +27,6 @@
 #  ca_file: /etc/ssl/cert.pem
 #  ca_path: /etc/pki/tls/certs
 #
-#  The self_signed_cert option is deprecated
-#  When it's set to true, any certificate is accepted, which may make machine-in-the-middle attack possible
-#  Certificates specified in ca_file and ca_path are trusted anyway even if they are self-signed
-#  Issue: https://gitlab.com/gitlab-org/gitlab-shell/-/issues/120
-  self_signed_cert: false
 
 # File used as authorized_keys for gitlab user
 auth_file: "/home/git/.ssh/authorized_keys"
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -38,7 +38,6 @@
 	ReadTimeoutSeconds uint64 `yaml:"read_timeout"`
 	CaFile             string `yaml:"ca_file"`
 	CaPath             string `yaml:"ca_path"`
-	SelfSignedCert     bool   `yaml:"self_signed_cert"`
 }
 
 type Config struct {
@@ -106,7 +105,6 @@
 			c.GitlabRelativeURLRoot,
 			c.HttpSettings.CaFile,
 			c.HttpSettings.CaPath,
-			c.HttpSettings.SelfSignedCert,
 			c.HttpSettings.ReadTimeoutSeconds,
 			nil,
 		)
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1650571670 -7200
#      Thu Apr 21 22:07:50 2022 +0200
# Node ID 1731d21061f74aa89279aa2ec48ebdfe559f18d2
# Parent  5c5ea97a01d86c20b2843fa1b81d54a287a37fad
drop go 1.16 support

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -60,7 +60,7 @@
   image: golang:${GO_VERSION}
   parallel:
     matrix:
-      - GO_VERSION: ["1.16", "1.17", "1.18"]
+      - GO_VERSION: ["1.17", "1.18"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module gitlab.com/gitlab-org/gitlab-shell
 
-go 1.16
+go 1.17
 
 require (
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
@@ -19,4 +19,65 @@
 	gopkg.in/yaml.v2 v2.4.0
 )
 
+require (
+	cloud.google.com/go v0.92.2 // indirect
+	cloud.google.com/go/profiler v0.1.0 // indirect
+	cloud.google.com/go/trace v0.1.0 // indirect
+	contrib.go.opencensus.io/exporter/stackdriver v0.13.8 // indirect
+	github.com/DataDog/datadog-go v4.4.0+incompatible // indirect
+	github.com/DataDog/sketches-go v1.0.0 // indirect
+	github.com/Microsoft/go-winio v0.5.0 // indirect
+	github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
+	github.com/aws/aws-sdk-go v1.38.35 // indirect
+	github.com/beevik/ntp v0.3.0 // indirect
+	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
+	github.com/cespare/xxhash/v2 v2.1.1 // indirect
+	github.com/client9/reopen v1.0.0 // indirect
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/go-ole/go-ole v1.2.4 // indirect
+	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/google/go-cmp v0.5.6 // indirect
+	github.com/google/pprof v0.0.0-20210804190019-f964ff605595 // indirect
+	github.com/google/uuid v1.2.0 // indirect
+	github.com/googleapis/gax-go/v2 v2.0.5 // indirect
+	github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 // indirect
+	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 // indirect
+	github.com/lightstep/lightstep-tracer-go v0.25.0 // indirect
+	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
+	github.com/oklog/ulid/v2 v2.0.2 // indirect
+	github.com/opentracing/opentracing-go v1.2.0 // indirect
+	github.com/philhofer/fwd v1.1.1 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/prometheus/client_model v0.2.0 // indirect
+	github.com/prometheus/common v0.26.0 // indirect
+	github.com/prometheus/procfs v0.6.0 // indirect
+	github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect
+	github.com/shirou/gopsutil/v3 v3.21.2 // indirect
+	github.com/sirupsen/logrus v1.8.1 // indirect
+	github.com/tinylib/msgp v1.1.2 // indirect
+	github.com/tklauser/go-sysconf v0.3.4 // indirect
+	github.com/tklauser/numcpus v0.2.1 // indirect
+	github.com/uber/jaeger-client-go v2.29.1+incompatible // indirect
+	github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
+	go.opencensus.io v0.23.0 // indirect
+	go.uber.org/atomic v1.7.0 // indirect
+	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
+	golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a // indirect
+	golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 // indirect
+	golang.org/x/text v0.3.6 // indirect
+	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
+	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+	google.golang.org/api v0.54.0 // indirect
+	google.golang.org/appengine v1.6.7 // indirect
+	google.golang.org/genproto v0.0.0-20210813162853-db860fec028c // indirect
+	google.golang.org/protobuf v1.27.1 // indirect
+	gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // indirect
+	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
+)
+
 replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1650950261 0
#      Tue Apr 26 05:17:41 2022 +0000
# Node ID 8765ffeb4cd1ec3b83c1bee95cca77db68310ee6
# Parent  14d2e1cc1ddc0a2859d2100e599aac602b19f2ed
# Parent  1731d21061f74aa89279aa2ec48ebdfe559f18d2
Merge branch 'drop/go-1.16' into 'main'

drop go 1.16 support

See merge request gitlab-org/gitlab-shell!601

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -60,7 +60,7 @@
   image: golang:${GO_VERSION}
   parallel:
     matrix:
-      - GO_VERSION: ["1.16", "1.17", "1.18"]
+      - GO_VERSION: ["1.17", "1.18"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module gitlab.com/gitlab-org/gitlab-shell
 
-go 1.16
+go 1.17
 
 require (
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
@@ -19,4 +19,65 @@
 	gopkg.in/yaml.v2 v2.4.0
 )
 
+require (
+	cloud.google.com/go v0.92.2 // indirect
+	cloud.google.com/go/profiler v0.1.0 // indirect
+	cloud.google.com/go/trace v0.1.0 // indirect
+	contrib.go.opencensus.io/exporter/stackdriver v0.13.8 // indirect
+	github.com/DataDog/datadog-go v4.4.0+incompatible // indirect
+	github.com/DataDog/sketches-go v1.0.0 // indirect
+	github.com/Microsoft/go-winio v0.5.0 // indirect
+	github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
+	github.com/aws/aws-sdk-go v1.38.35 // indirect
+	github.com/beevik/ntp v0.3.0 // indirect
+	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
+	github.com/cespare/xxhash/v2 v2.1.1 // indirect
+	github.com/client9/reopen v1.0.0 // indirect
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/go-ole/go-ole v1.2.4 // indirect
+	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/google/go-cmp v0.5.6 // indirect
+	github.com/google/pprof v0.0.0-20210804190019-f964ff605595 // indirect
+	github.com/google/uuid v1.2.0 // indirect
+	github.com/googleapis/gax-go/v2 v2.0.5 // indirect
+	github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 // indirect
+	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 // indirect
+	github.com/lightstep/lightstep-tracer-go v0.25.0 // indirect
+	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
+	github.com/oklog/ulid/v2 v2.0.2 // indirect
+	github.com/opentracing/opentracing-go v1.2.0 // indirect
+	github.com/philhofer/fwd v1.1.1 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/prometheus/client_model v0.2.0 // indirect
+	github.com/prometheus/common v0.26.0 // indirect
+	github.com/prometheus/procfs v0.6.0 // indirect
+	github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect
+	github.com/shirou/gopsutil/v3 v3.21.2 // indirect
+	github.com/sirupsen/logrus v1.8.1 // indirect
+	github.com/tinylib/msgp v1.1.2 // indirect
+	github.com/tklauser/go-sysconf v0.3.4 // indirect
+	github.com/tklauser/numcpus v0.2.1 // indirect
+	github.com/uber/jaeger-client-go v2.29.1+incompatible // indirect
+	github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
+	go.opencensus.io v0.23.0 // indirect
+	go.uber.org/atomic v1.7.0 // indirect
+	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
+	golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a // indirect
+	golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 // indirect
+	golang.org/x/text v0.3.6 // indirect
+	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
+	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+	google.golang.org/api v0.54.0 // indirect
+	google.golang.org/appengine v1.6.7 // indirect
+	google.golang.org/genproto v0.0.0-20210813162853-db860fec028c // indirect
+	google.golang.org/protobuf v1.27.1 // indirect
+	gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // indirect
+	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
+)
+
 replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1649777758 -14400
#      Tue Apr 12 19:35:58 2022 +0400
# Node ID 3b998028e5d01f5e5479935b0d928664fed26593
# Parent  8765ffeb4cd1ec3b83c1bee95cca77db68310ee6
Add JWT token to GitLab Rails request

It is passed as a Gitlab-Shell-Api-Request header and uses
the same shared secret in order to encrypt the token

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -10,13 +10,19 @@
 	"path"
 	"strings"
 	"testing"
+	"time"
 
+	"github.com/golang-jwt/jwt/v4"
 	"github.com/stretchr/testify/require"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
 
+var (
+	secret = []byte("sssh, it's a secret")
+)
+
 func TestClients(t *testing.T) {
 	testhelper.PrepareTestRootDir(t)
 
@@ -57,12 +63,10 @@
 		t.Run(tc.desc, func(t *testing.T) {
 			url := tc.server(t, buildRequests(t, tc.relativeURLRoot))
 
-			secret := "sssh, it's a secret"
-
 			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", 1, nil)
 			require.NoError(t, err)
 
-			client, err := NewGitlabNetClient("", "", secret, httpClient)
+			client, err := NewGitlabNetClient("", "", string(secret), httpClient)
 			require.NoError(t, err)
 
 			testBrokenRequest(t, client)
@@ -71,6 +75,7 @@
 			testMissing(t, client)
 			testErrorMessage(t, client)
 			testAuthenticationHeader(t, client)
+			testJWTAuthenticationHeader(t, client)
 		})
 	}
 }
@@ -160,7 +165,7 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, "sssh, it's a secret", string(header))
+		require.Equal(t, secret, header)
 	})
 
 	t.Run("Authentication headers for POST", func(t *testing.T) {
@@ -175,7 +180,44 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, "sssh, it's a secret", string(header))
+		require.Equal(t, secret, header)
+	})
+}
+
+func testJWTAuthenticationHeader(t *testing.T, client *GitlabNetClient) {
+	verifyJWTToken := func(t *testing.T, response *http.Response) {
+		responseBody, err := io.ReadAll(response.Body)
+		require.NoError(t, err)
+
+		claims := &jwt.RegisteredClaims{}
+		token, err := jwt.ParseWithClaims(string(responseBody), claims, func(token *jwt.Token) (interface{}, error) {
+			return secret, nil
+		})
+		require.NoError(t, err)
+		require.True(t, token.Valid)
+		require.Equal(t, "gitlab-shell", claims.Issuer)
+		require.Equal(t, time.Now().Truncate(time.Second), claims.IssuedAt.Time, time.Second)
+		require.Equal(t, time.Now().Truncate(time.Second).Add(time.Minute), claims.ExpiresAt.Time, time.Second)
+	}
+
+	t.Run("JWT authentication headers for GET", func(t *testing.T) {
+		response, err := client.Get(context.Background(), "/jwt_auth")
+		require.NoError(t, err)
+		require.NotNil(t, response)
+
+		defer response.Body.Close()
+
+		verifyJWTToken(t, response)
+	})
+
+	t.Run("JWT authentication headers for POST", func(t *testing.T) {
+		response, err := client.Post(context.Background(), "/jwt_auth", map[string]string{})
+		require.NoError(t, err)
+		require.NotNil(t, response)
+
+		defer response.Body.Close()
+
+		verifyJWTToken(t, response)
 	})
 }
 
@@ -209,6 +251,12 @@
 			},
 		},
 		{
+			Path: "/api/v4/internal/jwt_auth",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				fmt.Fprint(w, r.Header.Get(apiSecretHeaderName))
+			},
+		},
+		{
 			Path: "/api/v4/internal/error",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -11,13 +11,18 @@
 	"strings"
 	"time"
 
+	"github.com/golang-jwt/jwt/v4"
+
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
-	internalApiPath  = "/api/v4/internal"
-	secretHeaderName = "Gitlab-Shared-Secret"
-	defaultUserAgent = "GitLab-Shell"
+	internalApiPath     = "/api/v4/internal"
+	secretHeaderName    = "Gitlab-Shared-Secret"
+	apiSecretHeaderName = "Gitlab-Shell-Api-Request"
+	defaultUserAgent    = "GitLab-Shell"
+	jwtTTL              = time.Minute
+	jwtIssuer           = "gitlab-shell"
 )
 
 type ErrorResponse struct {
@@ -121,10 +126,22 @@
 	if user != "" && password != "" {
 		request.SetBasicAuth(user, password)
 	}
+	secretBytes := []byte(c.secret)
 
-	encodedSecret := base64.StdEncoding.EncodeToString([]byte(c.secret))
+	encodedSecret := base64.StdEncoding.EncodeToString(secretBytes)
 	request.Header.Set(secretHeaderName, encodedSecret)
 
+	claims := jwt.RegisteredClaims{
+		Issuer:    jwtIssuer,
+		IssuedAt:  jwt.NewNumericDate(time.Now()),
+		ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtTTL)),
+	}
+	tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secretBytes)
+	if err != nil {
+		return nil, err
+	}
+	request.Header.Set(apiSecretHeaderName, tokenString)
+
 	request.Header.Add("Content-Type", "application/json")
 	request.Header.Add("User-Agent", c.userAgent)
 	request.Close = true
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@
 go 1.17
 
 require (
+	github.com/golang-jwt/jwt/v4 v4.4.1
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
 	github.com/mattn/go-shellwords v1.0.11
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -316,6 +316,8 @@
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
+github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1651028661 0
#      Wed Apr 27 03:04:21 2022 +0000
# Node ID b4864e1ab2682169d8909119ac7d8fec30700611
# Parent  8765ffeb4cd1ec3b83c1bee95cca77db68310ee6
# Parent  3b998028e5d01f5e5479935b0d928664fed26593
Merge branch 'id-gitlabnet-jwt' into 'main'

Add JWT token to GitLab Rails request

See merge request gitlab-org/gitlab-shell!596

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -10,13 +10,19 @@
 	"path"
 	"strings"
 	"testing"
+	"time"
 
+	"github.com/golang-jwt/jwt/v4"
 	"github.com/stretchr/testify/require"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
 
+var (
+	secret = []byte("sssh, it's a secret")
+)
+
 func TestClients(t *testing.T) {
 	testhelper.PrepareTestRootDir(t)
 
@@ -57,12 +63,10 @@
 		t.Run(tc.desc, func(t *testing.T) {
 			url := tc.server(t, buildRequests(t, tc.relativeURLRoot))
 
-			secret := "sssh, it's a secret"
-
 			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", 1, nil)
 			require.NoError(t, err)
 
-			client, err := NewGitlabNetClient("", "", secret, httpClient)
+			client, err := NewGitlabNetClient("", "", string(secret), httpClient)
 			require.NoError(t, err)
 
 			testBrokenRequest(t, client)
@@ -71,6 +75,7 @@
 			testMissing(t, client)
 			testErrorMessage(t, client)
 			testAuthenticationHeader(t, client)
+			testJWTAuthenticationHeader(t, client)
 		})
 	}
 }
@@ -160,7 +165,7 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, "sssh, it's a secret", string(header))
+		require.Equal(t, secret, header)
 	})
 
 	t.Run("Authentication headers for POST", func(t *testing.T) {
@@ -175,7 +180,44 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, "sssh, it's a secret", string(header))
+		require.Equal(t, secret, header)
+	})
+}
+
+func testJWTAuthenticationHeader(t *testing.T, client *GitlabNetClient) {
+	verifyJWTToken := func(t *testing.T, response *http.Response) {
+		responseBody, err := io.ReadAll(response.Body)
+		require.NoError(t, err)
+
+		claims := &jwt.RegisteredClaims{}
+		token, err := jwt.ParseWithClaims(string(responseBody), claims, func(token *jwt.Token) (interface{}, error) {
+			return secret, nil
+		})
+		require.NoError(t, err)
+		require.True(t, token.Valid)
+		require.Equal(t, "gitlab-shell", claims.Issuer)
+		require.Equal(t, time.Now().Truncate(time.Second), claims.IssuedAt.Time, time.Second)
+		require.Equal(t, time.Now().Truncate(time.Second).Add(time.Minute), claims.ExpiresAt.Time, time.Second)
+	}
+
+	t.Run("JWT authentication headers for GET", func(t *testing.T) {
+		response, err := client.Get(context.Background(), "/jwt_auth")
+		require.NoError(t, err)
+		require.NotNil(t, response)
+
+		defer response.Body.Close()
+
+		verifyJWTToken(t, response)
+	})
+
+	t.Run("JWT authentication headers for POST", func(t *testing.T) {
+		response, err := client.Post(context.Background(), "/jwt_auth", map[string]string{})
+		require.NoError(t, err)
+		require.NotNil(t, response)
+
+		defer response.Body.Close()
+
+		verifyJWTToken(t, response)
 	})
 }
 
@@ -209,6 +251,12 @@
 			},
 		},
 		{
+			Path: "/api/v4/internal/jwt_auth",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				fmt.Fprint(w, r.Header.Get(apiSecretHeaderName))
+			},
+		},
+		{
 			Path: "/api/v4/internal/error",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -11,13 +11,18 @@
 	"strings"
 	"time"
 
+	"github.com/golang-jwt/jwt/v4"
+
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
-	internalApiPath  = "/api/v4/internal"
-	secretHeaderName = "Gitlab-Shared-Secret"
-	defaultUserAgent = "GitLab-Shell"
+	internalApiPath     = "/api/v4/internal"
+	secretHeaderName    = "Gitlab-Shared-Secret"
+	apiSecretHeaderName = "Gitlab-Shell-Api-Request"
+	defaultUserAgent    = "GitLab-Shell"
+	jwtTTL              = time.Minute
+	jwtIssuer           = "gitlab-shell"
 )
 
 type ErrorResponse struct {
@@ -121,10 +126,22 @@
 	if user != "" && password != "" {
 		request.SetBasicAuth(user, password)
 	}
+	secretBytes := []byte(c.secret)
 
-	encodedSecret := base64.StdEncoding.EncodeToString([]byte(c.secret))
+	encodedSecret := base64.StdEncoding.EncodeToString(secretBytes)
 	request.Header.Set(secretHeaderName, encodedSecret)
 
+	claims := jwt.RegisteredClaims{
+		Issuer:    jwtIssuer,
+		IssuedAt:  jwt.NewNumericDate(time.Now()),
+		ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtTTL)),
+	}
+	tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secretBytes)
+	if err != nil {
+		return nil, err
+	}
+	request.Header.Set(apiSecretHeaderName, tokenString)
+
 	request.Header.Add("Content-Type", "application/json")
 	request.Header.Add("User-Agent", c.userAgent)
 	request.Close = true
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@
 go 1.17
 
 require (
+	github.com/golang-jwt/jwt/v4 v4.4.1
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
 	github.com/mattn/go-shellwords v1.0.11
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -316,6 +316,8 @@
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
+github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
# HG changeset patch
# User Vasilii Iakliushin <viakliushin@gitlab.com>
# Date 1650628326 -7200
#      Fri Apr 22 13:52:06 2022 +0200
# Node ID f3c8a64c2ceb8534f4477e2fcf7c186a3ba0371f
# Parent  8765ffeb4cd1ec3b83c1bee95cca77db68310ee6
Remove deprecated function NewHTTPClient

Contributes to https://gitlab.com/gitlab-org/gitlab-shell/-/issues/484

Changelog: removed

diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -14,7 +14,6 @@
 	"time"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
-	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/tracing"
 )
 
@@ -70,15 +69,6 @@
 	return nil
 }
 
-// Deprecated: use NewHTTPClientWithOpts - https://gitlab.com/gitlab-org/gitlab-shell/-/issues/484
-func NewHTTPClient(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, readTimeoutSeconds uint64) *HttpClient {
-	c, err := NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath, readTimeoutSeconds, nil)
-	if err != nil {
-		log.WithError(err).Error("new http client with opts")
-	}
-	return c
-}
-
 // NewHTTPClientWithOpts builds an HTTP client using the provided options
 func NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, readTimeoutSeconds uint64, opts []HTTPClientOpt) (*HttpClient, error) {
 	var transport *http.Transport
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1651031456 0
#      Wed Apr 27 03:50:56 2022 +0000
# Node ID f88343229f1a7b8ea007cd89a13062aae2af4e5d
# Parent  b4864e1ab2682169d8909119ac7d8fec30700611
# Parent  f3c8a64c2ceb8534f4477e2fcf7c186a3ba0371f
Merge branch '484_remove_outdated_func' into 'main'

Remove deprecated function NewHTTPClient

See merge request gitlab-org/gitlab-shell!603

diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -14,7 +14,6 @@
 	"time"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
-	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/tracing"
 )
 
@@ -70,15 +69,6 @@
 	return nil
 }
 
-// Deprecated: use NewHTTPClientWithOpts - https://gitlab.com/gitlab-org/gitlab-shell/-/issues/484
-func NewHTTPClient(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, readTimeoutSeconds uint64) *HttpClient {
-	c, err := NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath, readTimeoutSeconds, nil)
-	if err != nil {
-		log.WithError(err).Error("new http client with opts")
-	}
-	return c
-}
-
 // NewHTTPClientWithOpts builds an HTTP client using the provided options
 func NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, readTimeoutSeconds uint64, opts []HTTPClientOpt) (*HttpClient, error) {
 	var transport *http.Transport
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1651248601 -14400
#      Fri Apr 29 20:10:01 2022 +0400
# Node ID 2185e876d016c0962bd17d3761418b86237a07ef
# Parent  f88343229f1a7b8ea007cd89a13062aae2af4e5d
Release 13.26.0

- Add JWT token to GitLab Rails request !596
- Drop go 1.16 support !601
- Remove `self_signed_cert` option !602

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+v13.26.0
+
+- Add JWT token to GitLab Rails request !596
+- Drop go 1.16 support !601
+- Remove `self_signed_cert` option !602
+
 v13.25.2
 
 - Revert "Abort long-running unauthenticated SSH connections" !605
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.25.2
+13.26.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1651249091 0
#      Fri Apr 29 16:18:11 2022 +0000
# Node ID ef77ff827274a4897a5bfe7ca6dfe172502e541c
# Parent  f88343229f1a7b8ea007cd89a13062aae2af4e5d
# Parent  2185e876d016c0962bd17d3761418b86237a07ef
Merge branch 'id-release-13-26-0' into 'main'

Release 13.26.0

See merge request gitlab-org/gitlab-shell!608

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+v13.26.0
+
+- Add JWT token to GitLab Rails request !596
+- Drop go 1.16 support !601
+- Remove `self_signed_cert` option !602
+
 v13.25.2
 
 - Revert "Abort long-running unauthenticated SSH connections" !605
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.25.2
+13.26.0
# HG changeset patch
# User Jacob Vosmaer <jacob@gitlab.com>
# Date 1651484092 0
#      Mon May 02 09:34:52 2022 +0000
# Node ID 44f238a331bfd171b1b5282a4449d108fe71cc28
# Parent  ef77ff827274a4897a5bfe7ca6dfe172502e541c
Always use Gitaly sidechannel connections

Before this change, the GitLab internal API could use a boolean
response field to indicate whether gitlab-shell should make
sidechannel connections go Gitaly. We now ignore that response field
and always use sidechannel connections.

diff --git a/internal/command/receivepack/gitalycall_test.go b/internal/command/receivepack/gitalycall_test.go
--- a/internal/command/receivepack/gitalycall_test.go
+++ b/internal/command/receivepack/gitalycall_test.go
@@ -57,8 +57,10 @@
 			args.GitlabKeyId = tc.keyId
 		}
 
+		cfg := &config.Config{GitlabUrl: url}
+		cfg.GitalyClient.InitSidechannelRegistry(context.Background())
 		cmd := &Command{
-			Config:     &config.Config{GitlabUrl: url},
+			Config:     cfg,
 			Args:       args,
 			ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 		}
diff --git a/internal/command/uploadarchive/gitalycall_test.go b/internal/command/uploadarchive/gitalycall_test.go
--- a/internal/command/uploadarchive/gitalycall_test.go
+++ b/internal/command/uploadarchive/gitalycall_test.go
@@ -41,8 +41,10 @@
 		Env:         env,
 	}
 
+	cfg := &config.Config{GitlabUrl: url}
+	cfg.GitalyClient.InitSidechannelRegistry(context.Background())
 	cmd := &Command{
-		Config:     &config.Config{GitlabUrl: url},
+		Config:     cfg,
 		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -15,24 +15,7 @@
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
 	gc := handler.NewGitalyCommand(c.Config, string(commandargs.UploadPack), response)
 
-	if response.Gitaly.UseSidechannel {
-		request := &pb.SSHUploadPackWithSidechannelRequest{
-			Repository:       &response.Gitaly.Repo,
-			GitProtocol:      c.Args.Env.GitProtocolVersion,
-			GitConfigOptions: response.GitConfigOptions,
-		}
-
-		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
-			ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
-			defer cancel()
-
-			registry := c.Config.GitalyClient.SidechannelRegistry
-			rw := c.ReadWriter
-			return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
-		})
-	}
-
-	request := &pb.SSHUploadPackRequest{
+	request := &pb.SSHUploadPackWithSidechannelRequest{
 		Repository:       &response.Gitaly.Repo,
 		GitProtocol:      c.Args.Env.GitProtocolVersion,
 		GitConfigOptions: response.GitConfigOptions,
@@ -42,7 +25,8 @@
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
+		registry := c.Config.GitalyClient.SidechannelRegistry
 		rw := c.ReadWriter
-		return client.UploadPack(ctx, conn, rw.In, rw.Out, rw.ErrOut, request)
+		return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
 	})
 }
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -41,62 +41,6 @@
 		Env:         env,
 	}
 
-	cmd := &Command{
-		Config:     &config.Config{GitlabUrl: url},
-		Args:       args,
-		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
-	}
-
-	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
-	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
-
-	err := cmd.Execute(ctx)
-	require.NoError(t, err)
-
-	require.Equal(t, "UploadPack: "+repo, output.String())
-
-	for k, v := range map[string]string{
-		"gitaly-feature-cache_invalidator":        "true",
-		"gitaly-feature-inforef_uploadpack_cache": "false",
-		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-pack",
-		"key_id":                                  "123",
-		"user_id":                                 "1",
-		"remote_ip":                               "127.0.0.1",
-		"key_type":                                "key",
-	} {
-		actual := testServer.ReceivedMD[k]
-		require.Len(t, actual, 1)
-		require.Equal(t, v, actual[0])
-	}
-	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
-	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
-}
-
-func TestUploadPack_withSidechannel(t *testing.T) {
-	gitalyAddress, testServer := testserver.StartGitalyServer(t)
-
-	requests := requesthandlers.BuildAllowedWithGitalyHandlersWithSidechannel(t, gitalyAddress)
-	url := testserver.StartHttpServer(t, requests)
-
-	output := &bytes.Buffer{}
-	input := &bytes.Buffer{}
-
-	userId := "1"
-	repo := "group/repo"
-
-	env := sshenv.Env{
-		IsSSHConnection: true,
-		OriginalCommand: "git-upload-pack " + repo,
-		RemoteAddr:      "127.0.0.1",
-	}
-
-	args := &commandargs.Shell{
-		GitlabKeyId: userId,
-		CommandType: commandargs.UploadPack,
-		SshArgs:     []string{"git-upload-pack", repo},
-		Env:         env,
-	}
-
 	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
--- a/internal/gitaly/gitaly.go
+++ b/internal/gitaly/gitaly.go
@@ -21,10 +21,9 @@
 )
 
 type Command struct {
-	ServiceName     string
-	Address         string
-	Token           string
-	DialSidechannel bool
+	ServiceName string
+	Address     string
+	Token       string
 }
 
 type connectionsCache struct {
@@ -125,9 +124,5 @@
 		)
 	}
 
-	if cmd.DialSidechannel {
-		return client.DialSidechannel(ctx, cmd.Address, c.SidechannelRegistry, connOpts)
-	}
-
-	return client.DialContext(ctx, cmd.Address, connOpts)
+	return client.DialSidechannel(ctx, cmd.Address, c.SidechannelRegistry, connOpts)
 }
diff --git a/internal/gitaly/gitaly_test.go b/internal/gitaly/gitaly_test.go
--- a/internal/gitaly/gitaly_test.go
+++ b/internal/gitaly/gitaly_test.go
@@ -13,7 +13,7 @@
 func TestPrometheusMetrics(t *testing.T) {
 	metrics.GitalyConnectionsTotal.Reset()
 
-	c := &Client{}
+	c := newClient()
 
 	cmd := Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9999"}
 	c.newConnection(context.Background(), cmd)
@@ -31,7 +31,7 @@
 }
 
 func TestCachedConnections(t *testing.T) {
-	c := &Client{}
+	c := newClient()
 
 	require.Len(t, c.cache.connections, 0)
 
@@ -51,3 +51,9 @@
 	require.NoError(t, err)
 	require.Len(t, c.cache.connections, 2)
 }
+
+func newClient() *Client {
+	c := &Client{}
+	c.InitSidechannelRegistry(context.Background())
+	return c
+}
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -32,11 +32,10 @@
 }
 
 type Gitaly struct {
-	Repo           pb.Repository     `json:"repository"`
-	Address        string            `json:"address"`
-	Token          string            `json:"token"`
-	Features       map[string]string `json:"features"`
-	UseSidechannel bool              `json:"use_sidechannel"`
+	Repo     pb.Repository     `json:"repository"`
+	Address  string            `json:"address"`
+	Token    string            `json:"token"`
+	Features map[string]string `json:"features"`
 }
 
 type CustomPayloadData struct {
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -86,41 +86,6 @@
 	}
 }
 
-func TestSidechannelFlag(t *testing.T) {
-	okResponse := testResponse{body: responseBody(t, "allowed_sidechannel.json"), status: http.StatusOK}
-	client := setup(t,
-		map[string]testResponse{"first": okResponse},
-		map[string]testResponse{"1": okResponse},
-	)
-
-	testCases := []struct {
-		desc string
-		args *commandargs.Shell
-		who  string
-	}{
-		{
-			desc: "Provide key id within the request",
-			args: &commandargs.Shell{GitlabKeyId: "1"},
-			who:  "key-1",
-		}, {
-			desc: "Provide username within the request",
-			args: &commandargs.Shell{GitlabUsername: "first"},
-			who:  "user-1",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			result, err := client.Verify(context.Background(), tc.args, uploadPackAction, repo)
-			require.NoError(t, err)
-
-			response := buildExpectedResponse(tc.who)
-			response.Gitaly.UseSidechannel = true
-			require.Equal(t, response, result)
-		})
-	}
-}
-
 func TestGeoPushGetCustomAction(t *testing.T) {
 	client := setup(t, map[string]testResponse{
 		"custom": {
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -33,10 +33,9 @@
 
 func NewGitalyCommand(cfg *config.Config, serviceName string, response *accessverifier.Response) *GitalyCommand {
 	gc := gitaly.Command{
-		ServiceName:     serviceName,
-		Address:         response.Gitaly.Address,
-		Token:           response.Gitaly.Token,
-		DialSidechannel: response.Gitaly.UseSidechannel,
+		ServiceName: serviceName,
+		Address:     response.Gitaly.Address,
+		Token:       response.Gitaly.Token,
 	}
 
 	return &GitalyCommand{Config: cfg, Response: response, Command: gc}
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -29,7 +29,7 @@
 
 func TestRunGitalyCommand(t *testing.T) {
 	cmd := NewGitalyCommand(
-		&config.Config{},
+		newConfig(),
 		string(commandargs.UploadPack),
 		&accessverifier.Response{
 			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
@@ -46,14 +46,12 @@
 
 func TestCachingOfGitalyConnections(t *testing.T) {
 	ctx := context.Background()
-	cfg := &config.Config{}
-	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+	cfg := newConfig()
 	response := &accessverifier.Response{
 		Username: "user",
 		Gitaly: accessverifier.Gitaly{
-			Address:        "tcp://localhost:9999",
-			Token:          "token",
-			UseSidechannel: true,
+			Address: "tcp://localhost:9999",
+			Token:   "token",
 		},
 	}
 
@@ -71,7 +69,7 @@
 }
 
 func TestMissingGitalyAddress(t *testing.T) {
-	cmd := GitalyCommand{Config: &config.Config{}}
+	cmd := GitalyCommand{Config: newConfig()}
 
 	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, nil))
 	require.EqualError(t, err, "no gitaly_address given")
@@ -79,7 +77,7 @@
 
 func TestUnavailableGitalyErr(t *testing.T) {
 	cmd := NewGitalyCommand(
-		&config.Config{},
+		newConfig(),
 		string(commandargs.UploadPack),
 		&accessverifier.Response{
 			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
@@ -101,7 +99,7 @@
 		{
 			name: "gitaly_feature_flags",
 			gc: NewGitalyCommand(
-				&config.Config{},
+				newConfig(),
 				string(commandargs.UploadPack),
 				&accessverifier.Response{
 					Gitaly: accessverifier.Gitaly{
@@ -209,3 +207,9 @@
 		})
 	}
 }
+
+func newConfig() *config.Config {
+	cfg := &config.Config{}
+	cfg.GitalyClient.InitSidechannelRegistry(context.Background())
+	return cfg
+}
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -29,14 +29,6 @@
 }
 
 func BuildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
-	return buildAllowedWithGitalyHandlers(t, gitalyAddress, false)
-}
-
-func BuildAllowedWithGitalyHandlersWithSidechannel(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
-	return buildAllowedWithGitalyHandlers(t, gitalyAddress, true)
-}
-
-func buildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string, useSidechannel bool) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/allowed",
@@ -64,9 +56,6 @@
 						},
 					},
 				}
-				if useSidechannel {
-					body["gitaly"].(map[string]interface{})["use_sidechannel"] = true
-				}
 				require.NoError(t, json.NewEncoder(w).Encode(body))
 			},
 		},
diff --git a/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json b/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
deleted file mode 100644
--- a/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-	"status": true,
-	"gl_repository": "project-26",
-	"gl_project_path": "group/private",
-	"gl_id": "user-1",
-	"gl_username": "root",
-	"git_config_options": ["option"],
-	"gitaly": {
-		"repository": {
-			"storage_name": "default",
-			"relative_path": "@hashed/5f/9c/5f9c4ab08cac7457e9111a30e4664920607ea2c115a1433d7be98e97e64244ca.git",
-			"git_object_directory": "path/to/git_object_directory",
-			"git_alternate_object_directories": ["path/to/git_alternate_object_directory"],
-			"gl_repository": "project-26",
-			"gl_project_path": "group/private"
-		},
-		"address": "unix:gitaly.socket",
-		"token": "token",
-               "use_sidechannel": true
-	},
-	"git_protocol": "protocol",
-	"gl_console_messages": ["console", "message"]
-}
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1651640158 0
#      Wed May 04 04:55:58 2022 +0000
# Node ID b41ecc049cf116629da59b050505665a3cdce3dd
# Parent  ef77ff827274a4897a5bfe7ca6dfe172502e541c
# Parent  44f238a331bfd171b1b5282a4449d108fe71cc28
Merge branch 'jv-always-use-sidechannel' into 'main'

Always use Gitaly sidechannel connections

See merge request gitlab-org/gitlab-shell!567

diff --git a/internal/command/receivepack/gitalycall_test.go b/internal/command/receivepack/gitalycall_test.go
--- a/internal/command/receivepack/gitalycall_test.go
+++ b/internal/command/receivepack/gitalycall_test.go
@@ -57,8 +57,10 @@
 			args.GitlabKeyId = tc.keyId
 		}
 
+		cfg := &config.Config{GitlabUrl: url}
+		cfg.GitalyClient.InitSidechannelRegistry(context.Background())
 		cmd := &Command{
-			Config:     &config.Config{GitlabUrl: url},
+			Config:     cfg,
 			Args:       args,
 			ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 		}
diff --git a/internal/command/uploadarchive/gitalycall_test.go b/internal/command/uploadarchive/gitalycall_test.go
--- a/internal/command/uploadarchive/gitalycall_test.go
+++ b/internal/command/uploadarchive/gitalycall_test.go
@@ -41,8 +41,10 @@
 		Env:         env,
 	}
 
+	cfg := &config.Config{GitlabUrl: url}
+	cfg.GitalyClient.InitSidechannelRegistry(context.Background())
 	cmd := &Command{
-		Config:     &config.Config{GitlabUrl: url},
+		Config:     cfg,
 		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -15,24 +15,7 @@
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
 	gc := handler.NewGitalyCommand(c.Config, string(commandargs.UploadPack), response)
 
-	if response.Gitaly.UseSidechannel {
-		request := &pb.SSHUploadPackWithSidechannelRequest{
-			Repository:       &response.Gitaly.Repo,
-			GitProtocol:      c.Args.Env.GitProtocolVersion,
-			GitConfigOptions: response.GitConfigOptions,
-		}
-
-		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
-			ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
-			defer cancel()
-
-			registry := c.Config.GitalyClient.SidechannelRegistry
-			rw := c.ReadWriter
-			return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
-		})
-	}
-
-	request := &pb.SSHUploadPackRequest{
+	request := &pb.SSHUploadPackWithSidechannelRequest{
 		Repository:       &response.Gitaly.Repo,
 		GitProtocol:      c.Args.Env.GitProtocolVersion,
 		GitConfigOptions: response.GitConfigOptions,
@@ -42,7 +25,8 @@
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
+		registry := c.Config.GitalyClient.SidechannelRegistry
 		rw := c.ReadWriter
-		return client.UploadPack(ctx, conn, rw.In, rw.Out, rw.ErrOut, request)
+		return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
 	})
 }
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -41,62 +41,6 @@
 		Env:         env,
 	}
 
-	cmd := &Command{
-		Config:     &config.Config{GitlabUrl: url},
-		Args:       args,
-		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
-	}
-
-	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
-	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
-
-	err := cmd.Execute(ctx)
-	require.NoError(t, err)
-
-	require.Equal(t, "UploadPack: "+repo, output.String())
-
-	for k, v := range map[string]string{
-		"gitaly-feature-cache_invalidator":        "true",
-		"gitaly-feature-inforef_uploadpack_cache": "false",
-		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-pack",
-		"key_id":                                  "123",
-		"user_id":                                 "1",
-		"remote_ip":                               "127.0.0.1",
-		"key_type":                                "key",
-	} {
-		actual := testServer.ReceivedMD[k]
-		require.Len(t, actual, 1)
-		require.Equal(t, v, actual[0])
-	}
-	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
-	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
-}
-
-func TestUploadPack_withSidechannel(t *testing.T) {
-	gitalyAddress, testServer := testserver.StartGitalyServer(t)
-
-	requests := requesthandlers.BuildAllowedWithGitalyHandlersWithSidechannel(t, gitalyAddress)
-	url := testserver.StartHttpServer(t, requests)
-
-	output := &bytes.Buffer{}
-	input := &bytes.Buffer{}
-
-	userId := "1"
-	repo := "group/repo"
-
-	env := sshenv.Env{
-		IsSSHConnection: true,
-		OriginalCommand: "git-upload-pack " + repo,
-		RemoteAddr:      "127.0.0.1",
-	}
-
-	args := &commandargs.Shell{
-		GitlabKeyId: userId,
-		CommandType: commandargs.UploadPack,
-		SshArgs:     []string{"git-upload-pack", repo},
-		Env:         env,
-	}
-
 	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
--- a/internal/gitaly/gitaly.go
+++ b/internal/gitaly/gitaly.go
@@ -21,10 +21,9 @@
 )
 
 type Command struct {
-	ServiceName     string
-	Address         string
-	Token           string
-	DialSidechannel bool
+	ServiceName string
+	Address     string
+	Token       string
 }
 
 type connectionsCache struct {
@@ -125,9 +124,5 @@
 		)
 	}
 
-	if cmd.DialSidechannel {
-		return client.DialSidechannel(ctx, cmd.Address, c.SidechannelRegistry, connOpts)
-	}
-
-	return client.DialContext(ctx, cmd.Address, connOpts)
+	return client.DialSidechannel(ctx, cmd.Address, c.SidechannelRegistry, connOpts)
 }
diff --git a/internal/gitaly/gitaly_test.go b/internal/gitaly/gitaly_test.go
--- a/internal/gitaly/gitaly_test.go
+++ b/internal/gitaly/gitaly_test.go
@@ -13,7 +13,7 @@
 func TestPrometheusMetrics(t *testing.T) {
 	metrics.GitalyConnectionsTotal.Reset()
 
-	c := &Client{}
+	c := newClient()
 
 	cmd := Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9999"}
 	c.newConnection(context.Background(), cmd)
@@ -31,7 +31,7 @@
 }
 
 func TestCachedConnections(t *testing.T) {
-	c := &Client{}
+	c := newClient()
 
 	require.Len(t, c.cache.connections, 0)
 
@@ -51,3 +51,9 @@
 	require.NoError(t, err)
 	require.Len(t, c.cache.connections, 2)
 }
+
+func newClient() *Client {
+	c := &Client{}
+	c.InitSidechannelRegistry(context.Background())
+	return c
+}
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -32,11 +32,10 @@
 }
 
 type Gitaly struct {
-	Repo           pb.Repository     `json:"repository"`
-	Address        string            `json:"address"`
-	Token          string            `json:"token"`
-	Features       map[string]string `json:"features"`
-	UseSidechannel bool              `json:"use_sidechannel"`
+	Repo     pb.Repository     `json:"repository"`
+	Address  string            `json:"address"`
+	Token    string            `json:"token"`
+	Features map[string]string `json:"features"`
 }
 
 type CustomPayloadData struct {
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -86,41 +86,6 @@
 	}
 }
 
-func TestSidechannelFlag(t *testing.T) {
-	okResponse := testResponse{body: responseBody(t, "allowed_sidechannel.json"), status: http.StatusOK}
-	client := setup(t,
-		map[string]testResponse{"first": okResponse},
-		map[string]testResponse{"1": okResponse},
-	)
-
-	testCases := []struct {
-		desc string
-		args *commandargs.Shell
-		who  string
-	}{
-		{
-			desc: "Provide key id within the request",
-			args: &commandargs.Shell{GitlabKeyId: "1"},
-			who:  "key-1",
-		}, {
-			desc: "Provide username within the request",
-			args: &commandargs.Shell{GitlabUsername: "first"},
-			who:  "user-1",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			result, err := client.Verify(context.Background(), tc.args, uploadPackAction, repo)
-			require.NoError(t, err)
-
-			response := buildExpectedResponse(tc.who)
-			response.Gitaly.UseSidechannel = true
-			require.Equal(t, response, result)
-		})
-	}
-}
-
 func TestGeoPushGetCustomAction(t *testing.T) {
 	client := setup(t, map[string]testResponse{
 		"custom": {
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -33,10 +33,9 @@
 
 func NewGitalyCommand(cfg *config.Config, serviceName string, response *accessverifier.Response) *GitalyCommand {
 	gc := gitaly.Command{
-		ServiceName:     serviceName,
-		Address:         response.Gitaly.Address,
-		Token:           response.Gitaly.Token,
-		DialSidechannel: response.Gitaly.UseSidechannel,
+		ServiceName: serviceName,
+		Address:     response.Gitaly.Address,
+		Token:       response.Gitaly.Token,
 	}
 
 	return &GitalyCommand{Config: cfg, Response: response, Command: gc}
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -29,7 +29,7 @@
 
 func TestRunGitalyCommand(t *testing.T) {
 	cmd := NewGitalyCommand(
-		&config.Config{},
+		newConfig(),
 		string(commandargs.UploadPack),
 		&accessverifier.Response{
 			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
@@ -46,14 +46,12 @@
 
 func TestCachingOfGitalyConnections(t *testing.T) {
 	ctx := context.Background()
-	cfg := &config.Config{}
-	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+	cfg := newConfig()
 	response := &accessverifier.Response{
 		Username: "user",
 		Gitaly: accessverifier.Gitaly{
-			Address:        "tcp://localhost:9999",
-			Token:          "token",
-			UseSidechannel: true,
+			Address: "tcp://localhost:9999",
+			Token:   "token",
 		},
 	}
 
@@ -71,7 +69,7 @@
 }
 
 func TestMissingGitalyAddress(t *testing.T) {
-	cmd := GitalyCommand{Config: &config.Config{}}
+	cmd := GitalyCommand{Config: newConfig()}
 
 	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, nil))
 	require.EqualError(t, err, "no gitaly_address given")
@@ -79,7 +77,7 @@
 
 func TestUnavailableGitalyErr(t *testing.T) {
 	cmd := NewGitalyCommand(
-		&config.Config{},
+		newConfig(),
 		string(commandargs.UploadPack),
 		&accessverifier.Response{
 			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
@@ -101,7 +99,7 @@
 		{
 			name: "gitaly_feature_flags",
 			gc: NewGitalyCommand(
-				&config.Config{},
+				newConfig(),
 				string(commandargs.UploadPack),
 				&accessverifier.Response{
 					Gitaly: accessverifier.Gitaly{
@@ -209,3 +207,9 @@
 		})
 	}
 }
+
+func newConfig() *config.Config {
+	cfg := &config.Config{}
+	cfg.GitalyClient.InitSidechannelRegistry(context.Background())
+	return cfg
+}
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -29,14 +29,6 @@
 }
 
 func BuildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
-	return buildAllowedWithGitalyHandlers(t, gitalyAddress, false)
-}
-
-func BuildAllowedWithGitalyHandlersWithSidechannel(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
-	return buildAllowedWithGitalyHandlers(t, gitalyAddress, true)
-}
-
-func buildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string, useSidechannel bool) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/allowed",
@@ -64,9 +56,6 @@
 						},
 					},
 				}
-				if useSidechannel {
-					body["gitaly"].(map[string]interface{})["use_sidechannel"] = true
-				}
 				require.NoError(t, json.NewEncoder(w).Encode(body))
 			},
 		},
diff --git a/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json b/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
deleted file mode 100644
--- a/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-	"status": true,
-	"gl_repository": "project-26",
-	"gl_project_path": "group/private",
-	"gl_id": "user-1",
-	"gl_username": "root",
-	"git_config_options": ["option"],
-	"gitaly": {
-		"repository": {
-			"storage_name": "default",
-			"relative_path": "@hashed/5f/9c/5f9c4ab08cac7457e9111a30e4664920607ea2c115a1433d7be98e97e64244ca.git",
-			"git_object_directory": "path/to/git_object_directory",
-			"git_alternate_object_directories": ["path/to/git_alternate_object_directory"],
-			"gl_repository": "project-26",
-			"gl_project_path": "group/private"
-		},
-		"address": "unix:gitaly.socket",
-		"token": "token",
-               "use_sidechannel": true
-	},
-	"git_protocol": "protocol",
-	"gl_console_messages": ["console", "message"]
-}
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1651645849 -28800
#      Wed May 04 14:30:49 2022 +0800
# Node ID eb2615521afa008fc46f51ac8f1b3c901fd17a14
# Parent  b41ecc049cf116629da59b050505665a3cdce3dd
Release 14.0.0

Always use Gitaly sidechannel connections !567

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+v14.0.0
+- Always use Gitaly sidechannel connections !567
+
 v13.26.0
 
 - Add JWT token to GitLab Rails request !596
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.26.0
+14.0.0
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1651646584 0
#      Wed May 04 06:43:04 2022 +0000
# Node ID 7b17b1d00467952ade430e1753f00ff2b92a303c
# Parent  b41ecc049cf116629da59b050505665a3cdce3dd
# Parent  eb2615521afa008fc46f51ac8f1b3c901fd17a14
Merge branch 'pb-release-14-0-0' into 'main'

Release 14.0.0

See merge request gitlab-org/gitlab-shell!609

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+v14.0.0
+- Always use Gitaly sidechannel connections !567
+
 v13.26.0
 
 - Add JWT token to GitLab Rails request !596
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.26.0
+14.0.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1651050070 -14400
#      Wed Apr 27 13:01:10 2022 +0400
# Node ID c782ae82a861f685035658c042bf5253497b86e8
# Parent  7b17b1d00467952ade430e1753f00ff2b92a303c
Use labkit for FIPS check

New version of LabKit provides FIPS checks that we can use instead
of the custom code

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,11 @@
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
 
 ifeq (${FIPS_MODE}, 1)
-    BUILD_TAGS += boringcrypto
+    # boringcrypto tag is added automatically by golang-fips compiler
+    BUILD_TAGS += fips
+    # If the golang-fips compiler is built with CGO_ENABLED=0, this needs to be
+    # explicitly switched on.
+    export CGO_ENABLED=1
 endif
 
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)" -mod=mod
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -8,10 +8,10 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
+	"gitlab.com/gitlab-org/labkit/fips"
 	"gitlab.com/gitlab-org/labkit/log"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/boring"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -74,7 +74,7 @@
 	cmdName := reflect.TypeOf(cmd).String()
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("gitlab-shell: main: executing command")
-	boring.CheckBoring()
+	fips.Check()
 
 	if err := cmd.Execute(ctx); err != nil {
 		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -10,11 +10,11 @@
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.1
-	github.com/prometheus/client_golang v1.11.0
+	github.com/prometheus/client_golang v1.12.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
-	gitlab.com/gitlab-org/labkit v1.12.0
-	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
+	gitlab.com/gitlab-org/labkit v1.14.0
+	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/grpc v1.40.0
 	gopkg.in/yaml.v2 v2.4.0
@@ -33,7 +33,7 @@
 	github.com/beevik/ntp v0.3.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
-	github.com/cespare/xxhash/v2 v2.1.1 // indirect
+	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/client9/reopen v1.0.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/go-ole/go-ole v1.2.4 // indirect
@@ -55,8 +55,8 @@
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/prometheus/client_model v0.2.0 // indirect
-	github.com/prometheus/common v0.26.0 // indirect
-	github.com/prometheus/procfs v0.6.0 // indirect
+	github.com/prometheus/common v0.32.1 // indirect
+	github.com/prometheus/procfs v0.7.3 // indirect
 	github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect
 	github.com/shirou/gopsutil/v3 v3.21.2 // indirect
 	github.com/sirupsen/logrus v1.8.1 // indirect
@@ -69,8 +69,8 @@
 	go.uber.org/atomic v1.7.0 // indirect
 	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
 	golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a // indirect
-	golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 // indirect
-	golang.org/x/text v0.3.6 // indirect
+	golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
+	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	google.golang.org/api v0.54.0 // indirect
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -165,8 +165,9 @@
 github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -255,12 +256,13 @@
 github.com/getsentry/sentry-go v0.5.1/go.mod h1:B8H7x8TYDPkeWPRzGpIiFO97LZP6rL8A3hEt8lUItMw=
 github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
 github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
-github.com/getsentry/sentry-go v0.11.0/go.mod h1:KBQIxiZAetw62Cj8Ri964vAEWVdgfaUCn30Q3bCvANo=
+github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
 github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
+github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
 github.com/git-lfs/git-lfs v1.5.1-0.20210304194248-2e1d981afbe3/go.mod h1:8Xqs4mqL7o6xEnaXckIgELARTeK7RYtm3pBab7S79Js=
 github.com/git-lfs/gitobj/v2 v2.0.1/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
 github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
@@ -294,6 +296,7 @@
 github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
+github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@@ -316,6 +319,8 @@
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
+github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
 github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
 github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
@@ -531,6 +536,7 @@
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jteeuwen/go-bindata v3.0.8-0.20180305030458-6025e8de665b+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
@@ -577,6 +583,7 @@
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
+github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -603,6 +610,8 @@
 github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -611,6 +620,7 @@
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-shellwords v0.0.0-20190425161501-2444a32a19f4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
@@ -641,6 +651,7 @@
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/montanaflynn/stats v0.0.0-20151014174947-eeaced052adb/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -723,8 +734,9 @@
 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
-github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
+github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -737,15 +749,17 @@
 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
-github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
+github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
+github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
@@ -846,6 +860,7 @@
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
+github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
 github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
 github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
@@ -880,8 +895,8 @@
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
 gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
 gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
-gitlab.com/gitlab-org/labkit v1.12.0 h1:XVwGqXfHrhEr2bzZbYy/eA7xulAeyvk9HB3Q7nH2H4I=
-gitlab.com/gitlab-org/labkit v1.12.0/go.mod h1:tnvyKiPF/Y0MeBy+ACYWRWexOEQk6PTRGUc9GstcnXc=
+gitlab.com/gitlab-org/labkit v1.14.0 h1:LSrvHgybidPyH8fHnsy1GBghrLR4kFObFrtZwUfCgAI=
+gitlab.com/gitlab-org/labkit v1.14.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
@@ -1010,6 +1025,8 @@
 golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -1111,6 +1128,7 @@
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1125,8 +1143,11 @@
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1136,12 +1157,14 @@
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/internal/boring/boring.go b/internal/boring/boring.go
deleted file mode 100644
--- a/internal/boring/boring.go
+++ /dev/null
@@ -1,23 +0,0 @@
-//go:build boringcrypto
-// +build boringcrypto
-
-package boring
-
-import (
-	"crypto/boring"
-
-	"gitlab.com/gitlab-org/labkit/log"
-)
-
-// CheckBoring checks whether FIPS crypto has been enabled. For the FIPS Go
-// compiler in https://github.com/golang-fips/go, this requires that:
-//
-// 1. The kernel has FIPS enabled (e.g. `/proc/sys/crypto/fips_enabled` is 1).
-// 2. A system OpenSSL can be dynamically loaded via ldopen().
-func CheckBoring() {
-	if boring.Enabled() {
-		log.Info("FIPS mode is enabled. Using an external SSL library.")
-		return
-	}
-	log.Info("gitlab-shell was compiled with FIPS mode, but an external SSL library was not enabled.")
-}
diff --git a/internal/boring/notboring.go b/internal/boring/notboring.go
deleted file mode 100644
--- a/internal/boring/notboring.go
+++ /dev/null
@@ -1,9 +0,0 @@
-//go:build !boringcrypto
-// +build !boringcrypto
-
-package boring
-
-// CheckBoring does nothing when the boringcrypto tag is not in the
-// build.
-func CheckBoring() {
-}
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1651805059 0
#      Fri May 06 02:44:19 2022 +0000
# Node ID fd3b6e6ae179b4abd186a73608301ad17e4ec29c
# Parent  7b17b1d00467952ade430e1753f00ff2b92a303c
# Parent  c782ae82a861f685035658c042bf5253497b86e8
Merge branch 'id-fips-labkit' into 'main'

Use labkit for FIPS check

See merge request gitlab-org/gitlab-shell!607

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,11 @@
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
 
 ifeq (${FIPS_MODE}, 1)
-    BUILD_TAGS += boringcrypto
+    # boringcrypto tag is added automatically by golang-fips compiler
+    BUILD_TAGS += fips
+    # If the golang-fips compiler is built with CGO_ENABLED=0, this needs to be
+    # explicitly switched on.
+    export CGO_ENABLED=1
 endif
 
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)" -mod=mod
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -8,10 +8,10 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
+	"gitlab.com/gitlab-org/labkit/fips"
 	"gitlab.com/gitlab-org/labkit/log"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/boring"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -74,7 +74,7 @@
 	cmdName := reflect.TypeOf(cmd).String()
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("gitlab-shell: main: executing command")
-	boring.CheckBoring()
+	fips.Check()
 
 	if err := cmd.Execute(ctx); err != nil {
 		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -10,11 +10,11 @@
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.1
-	github.com/prometheus/client_golang v1.11.0
+	github.com/prometheus/client_golang v1.12.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
-	gitlab.com/gitlab-org/labkit v1.12.0
-	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
+	gitlab.com/gitlab-org/labkit v1.14.0
+	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/grpc v1.40.0
 	gopkg.in/yaml.v2 v2.4.0
@@ -33,7 +33,7 @@
 	github.com/beevik/ntp v0.3.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
-	github.com/cespare/xxhash/v2 v2.1.1 // indirect
+	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/client9/reopen v1.0.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/go-ole/go-ole v1.2.4 // indirect
@@ -55,8 +55,8 @@
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/prometheus/client_model v0.2.0 // indirect
-	github.com/prometheus/common v0.26.0 // indirect
-	github.com/prometheus/procfs v0.6.0 // indirect
+	github.com/prometheus/common v0.32.1 // indirect
+	github.com/prometheus/procfs v0.7.3 // indirect
 	github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect
 	github.com/shirou/gopsutil/v3 v3.21.2 // indirect
 	github.com/sirupsen/logrus v1.8.1 // indirect
@@ -69,8 +69,8 @@
 	go.uber.org/atomic v1.7.0 // indirect
 	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
 	golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a // indirect
-	golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 // indirect
-	golang.org/x/text v0.3.6 // indirect
+	golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
+	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	google.golang.org/api v0.54.0 // indirect
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -165,8 +165,9 @@
 github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -255,12 +256,13 @@
 github.com/getsentry/sentry-go v0.5.1/go.mod h1:B8H7x8TYDPkeWPRzGpIiFO97LZP6rL8A3hEt8lUItMw=
 github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
 github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
-github.com/getsentry/sentry-go v0.11.0/go.mod h1:KBQIxiZAetw62Cj8Ri964vAEWVdgfaUCn30Q3bCvANo=
+github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
 github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
+github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
 github.com/git-lfs/git-lfs v1.5.1-0.20210304194248-2e1d981afbe3/go.mod h1:8Xqs4mqL7o6xEnaXckIgELARTeK7RYtm3pBab7S79Js=
 github.com/git-lfs/gitobj/v2 v2.0.1/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
 github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
@@ -294,6 +296,7 @@
 github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
+github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@@ -316,6 +319,8 @@
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
+github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
 github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
 github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
@@ -531,6 +536,7 @@
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jteeuwen/go-bindata v3.0.8-0.20180305030458-6025e8de665b+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
@@ -577,6 +583,7 @@
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
+github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -603,6 +610,8 @@
 github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -611,6 +620,7 @@
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-shellwords v0.0.0-20190425161501-2444a32a19f4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
@@ -641,6 +651,7 @@
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/montanaflynn/stats v0.0.0-20151014174947-eeaced052adb/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -723,8 +734,9 @@
 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
-github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
+github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -737,15 +749,17 @@
 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
-github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
+github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
+github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
@@ -846,6 +860,7 @@
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
+github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
 github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
 github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
@@ -880,8 +895,8 @@
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
 gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
 gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
-gitlab.com/gitlab-org/labkit v1.12.0 h1:XVwGqXfHrhEr2bzZbYy/eA7xulAeyvk9HB3Q7nH2H4I=
-gitlab.com/gitlab-org/labkit v1.12.0/go.mod h1:tnvyKiPF/Y0MeBy+ACYWRWexOEQk6PTRGUc9GstcnXc=
+gitlab.com/gitlab-org/labkit v1.14.0 h1:LSrvHgybidPyH8fHnsy1GBghrLR4kFObFrtZwUfCgAI=
+gitlab.com/gitlab-org/labkit v1.14.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
@@ -1010,6 +1025,8 @@
 golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -1111,6 +1128,7 @@
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1125,8 +1143,11 @@
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1136,12 +1157,14 @@
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/internal/boring/boring.go b/internal/boring/boring.go
deleted file mode 100644
--- a/internal/boring/boring.go
+++ /dev/null
@@ -1,23 +0,0 @@
-//go:build boringcrypto
-// +build boringcrypto
-
-package boring
-
-import (
-	"crypto/boring"
-
-	"gitlab.com/gitlab-org/labkit/log"
-)
-
-// CheckBoring checks whether FIPS crypto has been enabled. For the FIPS Go
-// compiler in https://github.com/golang-fips/go, this requires that:
-//
-// 1. The kernel has FIPS enabled (e.g. `/proc/sys/crypto/fips_enabled` is 1).
-// 2. A system OpenSSL can be dynamically loaded via ldopen().
-func CheckBoring() {
-	if boring.Enabled() {
-		log.Info("FIPS mode is enabled. Using an external SSL library.")
-		return
-	}
-	log.Info("gitlab-shell was compiled with FIPS mode, but an external SSL library was not enabled.")
-}
diff --git a/internal/boring/notboring.go b/internal/boring/notboring.go
deleted file mode 100644
--- a/internal/boring/notboring.go
+++ /dev/null
@@ -1,9 +0,0 @@
-//go:build !boringcrypto
-// +build !boringcrypto
-
-package boring
-
-// CheckBoring does nothing when the boringcrypto tag is not in the
-// build.
-func CheckBoring() {
-}
# HG changeset patch
# User Sean Carroll <scarroll@gitlab.com>
# Date 1651840720 0
#      Fri May 06 12:38:40 2022 +0000
# Node ID 9c034b5890c5768d74b636fbd3713ceaa1acfb13
# Parent  fd3b6e6ae179b4abd186a73608301ad17e4ec29c
Remove departed team member from CODEOWNERS

diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -1,4 +1,4 @@
-* @ashmckenzie @igor.drozdov @nick.thomas @patrickbajao
+* @ashmckenzie @igor.drozdov @patrickbajao
 
 [Documentation]
 *.md @aqualls
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1651842571 0
#      Fri May 06 13:09:31 2022 +0000
# Node ID 6655fe3f183fb1bbda190dda9a14c3619c4e1d01
# Parent  fd3b6e6ae179b4abd186a73608301ad17e4ec29c
# Parent  9c034b5890c5768d74b636fbd3713ceaa1acfb13
Merge branch 'sean_carroll-main-patch-02429' into 'main'

Remove departed team member from CODEOWNERS

See merge request gitlab-org/gitlab-shell!615

diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -1,4 +1,4 @@
-* @ashmckenzie @igor.drozdov @nick.thomas @patrickbajao
+* @ashmckenzie @igor.drozdov @patrickbajao
 
 [Documentation]
 *.md @aqualls
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1652079151 25200
#      Sun May 08 23:52:31 2022 -0700
# Node ID 3fd7d5cd3ee2be595d3233f543340940a0d762b7
# Parent  6712c68c42156ec09eefaab72446f498341d0464
Fix check_ip argument when gitlab-sshd used with PROXY protocol

When gitlab-sshd were used with the PROXY protocol, the `check_ip`
argument passed to `/api/v4/internal/allowed` was the Go remote
address, which is a host and port combination
(e.g. 127.0.0.1:12345). As a result, This prevents IP restrictions
from working properly on Rails. We fix this by stripping out the port
if it is present.

When OpenSSH is used, this is not an issue because the IP address
is extracted from `SSH_CONNECTION`.

Changelog: fixed

diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -3,6 +3,7 @@
 import (
 	"context"
 	"fmt"
+	"net"
 	"net/http"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
@@ -86,7 +87,7 @@
 		request.KeyId = args.GitlabKeyId
 	}
 
-	request.CheckIp = args.Env.RemoteAddr
+	request.CheckIp = parseIP(args.Env.RemoteAddr)
 
 	response, err := c.client.Post(ctx, "/allowed", request)
 	if err != nil {
@@ -117,3 +118,18 @@
 func (r *Response) IsCustomAction() bool {
 	return r.StatusCode == http.StatusMultipleChoices
 }
+
+func parseIP(remoteAddr string) string {
+	// The remoteAddr field can be filled by:
+	// 1. An IP address via the SSH_CONNECTION environment variable
+	// 2. A host:port combination via the PROXY protocol
+	ip, _, err := net.SplitHostPort(remoteAddr)
+
+	// If we don't have a port or can't parse this address for some reason,
+	// just return the original string.
+	if err != nil {
+		return remoteAddr
+	}
+
+	return ip
+}
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -14,6 +14,7 @@
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
 
@@ -215,6 +216,51 @@
 	}
 }
 
+func TestCheckIP(t *testing.T) {
+	testCases := []struct {
+		desc            string
+		remoteAddr      string
+		expectedCheckIp string
+	}{
+		{
+			desc:            "IPv4 address",
+			remoteAddr:      "18.245.0.42",
+			expectedCheckIp: "18.245.0.42",
+		},
+		{
+			desc:            "IPv6 address",
+			remoteAddr:      "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+			expectedCheckIp: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+		},
+		{
+			desc:            "Host and port",
+			remoteAddr:      "18.245.0.42:6345",
+			expectedCheckIp: "18.245.0.42",
+		},
+		{
+			desc:            "IPv6 host and port",
+			remoteAddr:      "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80",
+			expectedCheckIp: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+		},
+		{
+			desc:            "Bad remote addr",
+			remoteAddr:      "[127.0",
+			expectedCheckIp: "[127.0",
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			client := setupWithApiInspector(t,
+				func(r *Request) {
+					require.Equal(t, tc.expectedCheckIp, r.CheckIp)
+				})
+
+			sshEnv := sshenv.Env{RemoteAddr: tc.remoteAddr}
+			client.Verify(context.Background(), &commandargs.Shell{Env: sshEnv}, uploadPackAction, repo)
+		})
+	}
+}
+
 type testResponse struct {
 	body   []byte
 	status int
@@ -260,3 +306,29 @@
 
 	return client
 }
+
+func setupWithApiInspector(t *testing.T, inspector func(*Request)) *Client {
+	t.Helper()
+	requests := []testserver.TestRequestHandler{
+		{
+			Path: "/api/v4/internal/allowed",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				b, err := io.ReadAll(r.Body)
+				require.NoError(t, err)
+
+				var requestBody *Request
+				err = json.Unmarshal(b, &requestBody)
+				require.NoError(t, err)
+
+				inspector(requestBody)
+			},
+		},
+	}
+
+	url := testserver.StartSocketHttpServer(t, requests)
+
+	client, err := NewClient(&config.Config{GitlabUrl: url})
+	require.NoError(t, err)
+
+	return client
+}
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652089640 0
#      Mon May 09 09:47:20 2022 +0000
# Node ID bf31f366cc78f403ebaf42b21227d100afb3f9b7
# Parent  6655fe3f183fb1bbda190dda9a14c3619c4e1d01
# Parent  3fd7d5cd3ee2be595d3233f543340940a0d762b7
Merge branch 'sh-fix-remote-addr-handling' into 'main'

Fix check_ip argument when gitlab-sshd used with PROXY protocol

See merge request gitlab-org/gitlab-shell!616

diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -3,6 +3,7 @@
 import (
 	"context"
 	"fmt"
+	"net"
 	"net/http"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
@@ -85,7 +86,7 @@
 		request.KeyId = args.GitlabKeyId
 	}
 
-	request.CheckIp = args.Env.RemoteAddr
+	request.CheckIp = parseIP(args.Env.RemoteAddr)
 
 	response, err := c.client.Post(ctx, "/allowed", request)
 	if err != nil {
@@ -116,3 +117,18 @@
 func (r *Response) IsCustomAction() bool {
 	return r.StatusCode == http.StatusMultipleChoices
 }
+
+func parseIP(remoteAddr string) string {
+	// The remoteAddr field can be filled by:
+	// 1. An IP address via the SSH_CONNECTION environment variable
+	// 2. A host:port combination via the PROXY protocol
+	ip, _, err := net.SplitHostPort(remoteAddr)
+
+	// If we don't have a port or can't parse this address for some reason,
+	// just return the original string.
+	if err != nil {
+		return remoteAddr
+	}
+
+	return ip
+}
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -14,6 +14,7 @@
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
 
@@ -180,6 +181,51 @@
 	}
 }
 
+func TestCheckIP(t *testing.T) {
+	testCases := []struct {
+		desc            string
+		remoteAddr      string
+		expectedCheckIp string
+	}{
+		{
+			desc:            "IPv4 address",
+			remoteAddr:      "18.245.0.42",
+			expectedCheckIp: "18.245.0.42",
+		},
+		{
+			desc:            "IPv6 address",
+			remoteAddr:      "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+			expectedCheckIp: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+		},
+		{
+			desc:            "Host and port",
+			remoteAddr:      "18.245.0.42:6345",
+			expectedCheckIp: "18.245.0.42",
+		},
+		{
+			desc:            "IPv6 host and port",
+			remoteAddr:      "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80",
+			expectedCheckIp: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+		},
+		{
+			desc:            "Bad remote addr",
+			remoteAddr:      "[127.0",
+			expectedCheckIp: "[127.0",
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			client := setupWithApiInspector(t,
+				func(r *Request) {
+					require.Equal(t, tc.expectedCheckIp, r.CheckIp)
+				})
+
+			sshEnv := sshenv.Env{RemoteAddr: tc.remoteAddr}
+			client.Verify(context.Background(), &commandargs.Shell{Env: sshEnv}, uploadPackAction, repo)
+		})
+	}
+}
+
 type testResponse struct {
 	body   []byte
 	status int
@@ -225,3 +271,29 @@
 
 	return client
 }
+
+func setupWithApiInspector(t *testing.T, inspector func(*Request)) *Client {
+	t.Helper()
+	requests := []testserver.TestRequestHandler{
+		{
+			Path: "/api/v4/internal/allowed",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				b, err := io.ReadAll(r.Body)
+				require.NoError(t, err)
+
+				var requestBody *Request
+				err = json.Unmarshal(b, &requestBody)
+				require.NoError(t, err)
+
+				inspector(requestBody)
+			},
+		},
+	}
+
+	url := testserver.StartSocketHttpServer(t, requests)
+
+	client, err := NewClient(&config.Config{GitlabUrl: url})
+	require.NoError(t, err)
+
+	return client
+}
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1651822751 -14400
#      Fri May 06 11:39:11 2022 +0400
# Node ID c127d3a0f5fb8732e0ade48b8304d9dfd0b8a7a3
# Parent  7b17b1d00467952ade430e1753f00ff2b92a303c
Exclude authentication errors from apdex

Most of the time a connection fails due to the client's
misconfiguration or when a client cancels a request, so we
shouldn't treat them as an error

Warnings will help us to track the errors whether
they happened on the server-side

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -2,6 +2,7 @@
 
 import (
 	"context"
+	"time"
 
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
@@ -49,6 +50,10 @@
 		}
 
 		go func() {
+			defer func(started time.Time) {
+				metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
+			}(time.Now())
+
 			defer c.concurrentSessions.Release(1)
 
 			// Prevent a panic in a single session from taking out the whole server
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -146,19 +146,8 @@
 }
 
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
-	success := false
-
 	metrics.SshdConnectionsInFlight.Inc()
-	started := time.Now()
-	defer func() {
-		metrics.SshdConnectionsInFlight.Dec()
-		metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
-
-		metrics.SliSshdSessionsTotal.Inc()
-		if !success {
-			metrics.SliSshdSessionsErrorsTotal.Inc()
-		}
-	}()
+	defer metrics.SshdConnectionsInFlight.Dec()
 
 	remoteAddr := nconn.RemoteAddr().String()
 
@@ -174,6 +163,8 @@
 	defer func() {
 		if err := recover(); err != nil {
 			ctxlog.Warn("panic handling session")
+
+			metrics.SliSshdSessionsErrorsTotal.Inc()
 		}
 	}()
 
@@ -181,11 +172,12 @@
 
 	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
-		ctxlog.WithError(err).Error("server: handleConn: failed to initialize SSH connection")
+		ctxlog.WithError(err).Warn("server: handleConn: failed to initialize SSH connection")
 		return
 	}
 	go ssh.DiscardRequests(reqs)
 
+	started := time.Now()
 	var establishSessionDuration float64
 	conn := newConnection(s.Config.Server.ConcurrentSessionsLimit, remoteAddr)
 	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
@@ -199,9 +191,11 @@
 			remoteAddr:  remoteAddr,
 		}
 
+		metrics.SliSshdSessionsTotal.Inc()
 		session.handle(ctx, requests)
-
-		success = session.success
+		if !session.success {
+			metrics.SliSshdSessionsErrorsTotal.Inc()
+		}
 	})
 
 	ctxlog.WithFields(log.Fields{
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1652214221 0
#      Tue May 10 20:23:41 2022 +0000
# Node ID b77a725da3688447cf910650d0113fac90ac08e3
# Parent  bf31f366cc78f403ebaf42b21227d100afb3f9b7
# Parent  c127d3a0f5fb8732e0ade48b8304d9dfd0b8a7a3
Merge branch 'id-improve-errors-metrics' into 'main'

Exclude authentication errors from error rate

See merge request gitlab-org/gitlab-shell!611

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -2,6 +2,7 @@
 
 import (
 	"context"
+	"time"
 
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
@@ -49,6 +50,10 @@
 		}
 
 		go func() {
+			defer func(started time.Time) {
+				metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
+			}(time.Now())
+
 			defer c.concurrentSessions.Release(1)
 
 			// Prevent a panic in a single session from taking out the whole server
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -146,19 +146,8 @@
 }
 
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
-	success := false
-
 	metrics.SshdConnectionsInFlight.Inc()
-	started := time.Now()
-	defer func() {
-		metrics.SshdConnectionsInFlight.Dec()
-		metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
-
-		metrics.SliSshdSessionsTotal.Inc()
-		if !success {
-			metrics.SliSshdSessionsErrorsTotal.Inc()
-		}
-	}()
+	defer metrics.SshdConnectionsInFlight.Dec()
 
 	remoteAddr := nconn.RemoteAddr().String()
 
@@ -174,6 +163,8 @@
 	defer func() {
 		if err := recover(); err != nil {
 			ctxlog.Warn("panic handling session")
+
+			metrics.SliSshdSessionsErrorsTotal.Inc()
 		}
 	}()
 
@@ -181,11 +172,12 @@
 
 	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
-		ctxlog.WithError(err).Error("server: handleConn: failed to initialize SSH connection")
+		ctxlog.WithError(err).Warn("server: handleConn: failed to initialize SSH connection")
 		return
 	}
 	go ssh.DiscardRequests(reqs)
 
+	started := time.Now()
 	var establishSessionDuration float64
 	conn := newConnection(s.Config.Server.ConcurrentSessionsLimit, remoteAddr)
 	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
@@ -199,9 +191,11 @@
 			remoteAddr:  remoteAddr,
 		}
 
+		metrics.SliSshdSessionsTotal.Inc()
 		session.handle(ctx, requests)
-
-		success = session.success
+		if !session.success {
+			metrics.SliSshdSessionsErrorsTotal.Inc()
+		}
 	})
 
 	ctxlog.WithFields(log.Fields{
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652210182 -14400
#      Tue May 10 23:16:22 2022 +0400
# Node ID 9f6e632206a3bc753f510e0f1eeb40e5e919d842
# Parent  bf31f366cc78f403ebaf42b21227d100afb3f9b7
Make PROXY policy configurable

It would give us more flexibility when we decide to enable
PROXY protocol

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -69,6 +69,9 @@
   # Set to true if gitlab-sshd is being fronted by a load balancer that implements
   # the PROXY protocol.
   proxy_protocol: false
+  # Proxy protocol policy ("use", "require", "reject", "ignore"), "use" is the default value
+  # Values: https://github.com/pires/go-proxyproto/blob/195fedcfbfc1be163f3a0d507fac1709e9d81fed/policy.go#L20
+  proxy_policy: "use"
   # Address which the server listens on HTTP for monitoring/health checks. Defaults to localhost:9122.
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -24,6 +24,7 @@
 type ServerConfig struct {
 	Listen                  string   `yaml:"listen,omitempty"`
 	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
+	ProxyPolicy             string   `yaml:"proxy_policy,omitempty"`
 	WebListen               string   `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
 	GracePeriodSeconds      uint64   `yaml:"grace_period"`
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -5,6 +5,7 @@
 	"fmt"
 	"net"
 	"net/http"
+	"strings"
 	"sync"
 	"time"
 
@@ -95,7 +96,7 @@
 	if s.Config.Server.ProxyProtocol {
 		sshListener = &proxyproto.Listener{
 			Listener:          sshListener,
-			Policy:            unconditionalRequirePolicy,
+			Policy:            s.requirePolicy,
 			ReadHeaderTimeout: ProxyHeaderTimeout,
 		}
 
@@ -210,6 +211,17 @@
 	}).Info("server: handleConn: done")
 }
 
-func unconditionalRequirePolicy(_ net.Addr) (proxyproto.Policy, error) {
-	return proxyproto.REQUIRE, nil
+func (s *Server) requirePolicy(_ net.Addr) (proxyproto.Policy, error) {
+	// Set the Policy value based on config
+	// Values are taken from https://github.com/pires/go-proxyproto/blob/195fedcfbfc1be163f3a0d507fac1709e9d81fed/policy.go#L20
+	switch strings.ToLower(s.Config.Server.ProxyPolicy) {
+	case "require":
+		return proxyproto.REQUIRE, nil
+	case "ignore":
+		return proxyproto.IGNORE, nil
+	case "reject":
+		return proxyproto.REJECT, nil
+	default:
+		return proxyproto.USE, nil
+	}
 }
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -3,6 +3,7 @@
 import (
 	"context"
 	"fmt"
+	"net"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -10,6 +11,7 @@
 	"testing"
 	"time"
 
+	"github.com/pires/go-proxyproto"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
@@ -48,15 +50,101 @@
 }
 
 func TestListenAndServeRejectsPlainConnectionsWhenProxyProtocolEnabled(t *testing.T) {
-	setupServerWithProxyProtocolEnabled(t)
+	target, err := net.ResolveTCPAddr("tcp", serverUrl)
+	require.NoError(t, err)
 
-	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
-	if client != nil {
-		client.Close()
+	header := &proxyproto.Header{
+		Version:           2,
+		Command:           proxyproto.PROXY,
+		TransportProtocol: proxyproto.TCPv4,
+		SourceAddr: &net.TCPAddr{
+			IP:   net.ParseIP("10.1.1.1"),
+			Port: 1000,
+		},
+		DestinationAddr: target,
 	}
 
-	require.Error(t, err, "Expected plain SSH request to be failed")
-	require.Regexp(t, "ssh: handshake failed", err.Error())
+	testCases := []struct {
+		desc        string
+		proxyPolicy string
+		header      *proxyproto.Header
+		isRejected  bool
+	}{
+		{
+			desc:        "USE (default) without a header",
+			proxyPolicy: "",
+			header:      nil,
+			isRejected:  false,
+		},
+		{
+			desc:        "USE (default) with a header",
+			proxyPolicy: "",
+			header:      header,
+			isRejected:  false,
+		},
+		{
+			desc:        "REQUIRE without a header",
+			proxyPolicy: "require",
+			header:      nil,
+			isRejected:  true,
+		},
+		{
+			desc:        "REQUIRE with a header",
+			proxyPolicy: "require",
+			header:      header,
+			isRejected:  false,
+		},
+		{
+			desc:        "REJECT without a header",
+			proxyPolicy: "reject",
+			header:      nil,
+			isRejected:  false,
+		},
+		{
+			desc:        "REJECT with a header",
+			proxyPolicy: "reject",
+			header:      header,
+			isRejected:  true,
+		},
+		{
+			desc:        "IGNORE without a header",
+			proxyPolicy: "ignore",
+			header:      nil,
+			isRejected:  false,
+		},
+		{
+			desc:        "IGNORE with a header",
+			proxyPolicy: "ignore",
+			header:      header,
+			isRejected:  false,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			setupServerWithConfig(t, &config.Config{Server: config.ServerConfig{ProxyProtocol: true, ProxyPolicy: tc.proxyPolicy}})
+
+			conn, err := net.DialTCP("tcp", nil, target)
+			require.NoError(t, err)
+
+			if tc.header != nil {
+				_, err := header.WriteTo(conn)
+				require.NoError(t, err)
+			}
+
+			sshConn, _, _, err := ssh.NewClientConn(conn, serverUrl, clientConfig(t))
+			if sshConn != nil {
+				sshConn.Close()
+			}
+
+			if tc.isRejected {
+				require.Error(t, err, "Expected plain SSH request to be failed")
+				require.Regexp(t, "ssh: handshake failed", err.Error())
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
 }
 
 func TestCorrelationId(t *testing.T) {
@@ -140,12 +228,6 @@
 	return setupServerWithConfig(t, nil)
 }
 
-func setupServerWithProxyProtocolEnabled(t *testing.T) *Server {
-	t.Helper()
-
-	return setupServerWithConfig(t, &config.Config{Server: config.ServerConfig{ProxyProtocol: true}})
-}
-
 func setupServerWithConfig(t *testing.T, cfg *config.Config) *Server {
 	t.Helper()
 
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1652214306 0
#      Tue May 10 20:25:06 2022 +0000
# Node ID 8b5dd2943c0bcf478ea2f5b8c478638034736a67
# Parent  b77a725da3688447cf910650d0113fac90ac08e3
# Parent  9f6e632206a3bc753f510e0f1eeb40e5e919d842
Merge branch 'id-make-proxy-policy-configurable' into 'main'

Make PROXY policy configurable

See merge request gitlab-org/gitlab-shell!619

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -69,6 +69,9 @@
   # Set to true if gitlab-sshd is being fronted by a load balancer that implements
   # the PROXY protocol.
   proxy_protocol: false
+  # Proxy protocol policy ("use", "require", "reject", "ignore"), "use" is the default value
+  # Values: https://github.com/pires/go-proxyproto/blob/195fedcfbfc1be163f3a0d507fac1709e9d81fed/policy.go#L20
+  proxy_policy: "use"
   # Address which the server listens on HTTP for monitoring/health checks. Defaults to localhost:9122.
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -24,6 +24,7 @@
 type ServerConfig struct {
 	Listen                  string   `yaml:"listen,omitempty"`
 	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
+	ProxyPolicy             string   `yaml:"proxy_policy,omitempty"`
 	WebListen               string   `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
 	GracePeriodSeconds      uint64   `yaml:"grace_period"`
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -5,6 +5,7 @@
 	"fmt"
 	"net"
 	"net/http"
+	"strings"
 	"sync"
 	"time"
 
@@ -95,7 +96,7 @@
 	if s.Config.Server.ProxyProtocol {
 		sshListener = &proxyproto.Listener{
 			Listener:          sshListener,
-			Policy:            unconditionalRequirePolicy,
+			Policy:            s.requirePolicy,
 			ReadHeaderTimeout: ProxyHeaderTimeout,
 		}
 
@@ -204,6 +205,17 @@
 	}).Info("server: handleConn: done")
 }
 
-func unconditionalRequirePolicy(_ net.Addr) (proxyproto.Policy, error) {
-	return proxyproto.REQUIRE, nil
+func (s *Server) requirePolicy(_ net.Addr) (proxyproto.Policy, error) {
+	// Set the Policy value based on config
+	// Values are taken from https://github.com/pires/go-proxyproto/blob/195fedcfbfc1be163f3a0d507fac1709e9d81fed/policy.go#L20
+	switch strings.ToLower(s.Config.Server.ProxyPolicy) {
+	case "require":
+		return proxyproto.REQUIRE, nil
+	case "ignore":
+		return proxyproto.IGNORE, nil
+	case "reject":
+		return proxyproto.REJECT, nil
+	default:
+		return proxyproto.USE, nil
+	}
 }
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -3,6 +3,7 @@
 import (
 	"context"
 	"fmt"
+	"net"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -10,6 +11,7 @@
 	"testing"
 	"time"
 
+	"github.com/pires/go-proxyproto"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
@@ -48,15 +50,101 @@
 }
 
 func TestListenAndServeRejectsPlainConnectionsWhenProxyProtocolEnabled(t *testing.T) {
-	setupServerWithProxyProtocolEnabled(t)
+	target, err := net.ResolveTCPAddr("tcp", serverUrl)
+	require.NoError(t, err)
 
-	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
-	if client != nil {
-		client.Close()
+	header := &proxyproto.Header{
+		Version:           2,
+		Command:           proxyproto.PROXY,
+		TransportProtocol: proxyproto.TCPv4,
+		SourceAddr: &net.TCPAddr{
+			IP:   net.ParseIP("10.1.1.1"),
+			Port: 1000,
+		},
+		DestinationAddr: target,
 	}
 
-	require.Error(t, err, "Expected plain SSH request to be failed")
-	require.Regexp(t, "ssh: handshake failed", err.Error())
+	testCases := []struct {
+		desc        string
+		proxyPolicy string
+		header      *proxyproto.Header
+		isRejected  bool
+	}{
+		{
+			desc:        "USE (default) without a header",
+			proxyPolicy: "",
+			header:      nil,
+			isRejected:  false,
+		},
+		{
+			desc:        "USE (default) with a header",
+			proxyPolicy: "",
+			header:      header,
+			isRejected:  false,
+		},
+		{
+			desc:        "REQUIRE without a header",
+			proxyPolicy: "require",
+			header:      nil,
+			isRejected:  true,
+		},
+		{
+			desc:        "REQUIRE with a header",
+			proxyPolicy: "require",
+			header:      header,
+			isRejected:  false,
+		},
+		{
+			desc:        "REJECT without a header",
+			proxyPolicy: "reject",
+			header:      nil,
+			isRejected:  false,
+		},
+		{
+			desc:        "REJECT with a header",
+			proxyPolicy: "reject",
+			header:      header,
+			isRejected:  true,
+		},
+		{
+			desc:        "IGNORE without a header",
+			proxyPolicy: "ignore",
+			header:      nil,
+			isRejected:  false,
+		},
+		{
+			desc:        "IGNORE with a header",
+			proxyPolicy: "ignore",
+			header:      header,
+			isRejected:  false,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			setupServerWithConfig(t, &config.Config{Server: config.ServerConfig{ProxyProtocol: true, ProxyPolicy: tc.proxyPolicy}})
+
+			conn, err := net.DialTCP("tcp", nil, target)
+			require.NoError(t, err)
+
+			if tc.header != nil {
+				_, err := header.WriteTo(conn)
+				require.NoError(t, err)
+			}
+
+			sshConn, _, _, err := ssh.NewClientConn(conn, serverUrl, clientConfig(t))
+			if sshConn != nil {
+				sshConn.Close()
+			}
+
+			if tc.isRejected {
+				require.Error(t, err, "Expected plain SSH request to be failed")
+				require.Regexp(t, "ssh: handshake failed", err.Error())
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
 }
 
 func TestCorrelationId(t *testing.T) {
@@ -140,12 +228,6 @@
 	return setupServerWithConfig(t, nil)
 }
 
-func setupServerWithProxyProtocolEnabled(t *testing.T) *Server {
-	t.Helper()
-
-	return setupServerWithConfig(t, &config.Config{Server: config.ServerConfig{ProxyProtocol: true}})
-}
-
 func setupServerWithConfig(t *testing.T, cfg *config.Config) *Server {
 	t.Helper()
 
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1652222778 25200
#      Tue May 10 15:46:18 2022 -0700
# Node ID d7165070d89aab190f689fca39b42480954d3eef
# Parent  8b5dd2943c0bcf478ea2f5b8c478638034736a67
Release 14.1.0

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,4 +1,12 @@
+v14.1.0
+
+- Make PROXY policy configurable !619
+- Exclude authentication errors from apdex !611
+- Fix check_ip argument when gitlab-sshd used with PROXY protocol !616
+- Use labkit for FIPS check !607
+
 v14.0.0
+
 - Always use Gitaly sidechannel connections !567
 
 v13.26.0
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.0.0
+14.1.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652239007 0
#      Wed May 11 03:16:47 2022 +0000
# Node ID a611e711ddf6f21e2cea609256a5c49bb169b82c
# Parent  8b5dd2943c0bcf478ea2f5b8c478638034736a67
# Parent  d7165070d89aab190f689fca39b42480954d3eef
Merge branch 'sh-release-14.1.0' into 'main'

Release 14.1.0

See merge request gitlab-org/gitlab-shell!620

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,4 +1,12 @@
+v14.1.0
+
+- Make PROXY policy configurable !619
+- Exclude authentication errors from apdex !611
+- Fix check_ip argument when gitlab-sshd used with PROXY protocol !616
+- Use labkit for FIPS check !607
+
 v14.0.0
+
 - Always use Gitaly sidechannel connections !567
 
 v13.26.0
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.0.0
+14.1.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1651848330 -14400
#      Fri May 06 18:45:30 2022 +0400
# Node ID 4bc5a4df33667650f35fff21772e4686efbd8f05
# Parent  fd3b6e6ae179b4abd186a73608301ad17e4ec29c
Log the error that happens on sconn.Wait()

Warning level is used because a non-nil error is logged even for
successful scenarios

We plan to use it for debug reasons

diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -204,9 +204,11 @@
 		success = session.success
 	})
 
+	reason := sconn.Wait()
 	ctxlog.WithFields(log.Fields{
 		"duration_s":                   time.Since(started).Seconds(),
 		"establish_session_duration_s": establishSessionDuration,
+		"reason":                       reason,
 	}).Info("server: handleConn: done")
 }
 
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1652241740 0
#      Wed May 11 04:02:20 2022 +0000
# Node ID cc67d0a846971f52a0179ef69be4f8ebcd1a8ba4
# Parent  a611e711ddf6f21e2cea609256a5c49bb169b82c
# Parent  4bc5a4df33667650f35fff21772e4686efbd8f05
Merge branch 'id-sync-sshd-sessions' into 'main'

Log the error that happens on sconn.Wait()

See merge request gitlab-org/gitlab-shell!613

diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -199,9 +199,11 @@
 		}
 	})
 
+	reason := sconn.Wait()
 	ctxlog.WithFields(log.Fields{
 		"duration_s":                   time.Since(started).Seconds(),
 		"establish_session_duration_s": establishSessionDuration,
+		"reason":                       reason,
 	}).Info("server: handleConn: done")
 }
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652084433 -14400
#      Mon May 09 12:20:33 2022 +0400
# Node ID 7932a05741f89e3174ce9a497cbe1cc68ac7aefb
# Parent  fd3b6e6ae179b4abd186a73608301ad17e4ec29c
Use require.WithinDuration to fix flacky test

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -196,8 +196,8 @@
 		require.NoError(t, err)
 		require.True(t, token.Valid)
 		require.Equal(t, "gitlab-shell", claims.Issuer)
-		require.Equal(t, time.Now().Truncate(time.Second), claims.IssuedAt.Time, time.Second)
-		require.Equal(t, time.Now().Truncate(time.Second).Add(time.Minute), claims.ExpiresAt.Time, time.Second)
+		require.WithinDuration(t, time.Now().Truncate(time.Second), claims.IssuedAt.Time, time.Second)
+		require.WithinDuration(t, time.Now().Truncate(time.Second).Add(time.Minute), claims.ExpiresAt.Time, time.Second)
 	}
 
 	t.Run("JWT authentication headers for GET", func(t *testing.T) {
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1652243052 0
#      Wed May 11 04:24:12 2022 +0000
# Node ID 33198b2ae2a7aea3efa4f1940c0dc26af7a2ac39
# Parent  cc67d0a846971f52a0179ef69be4f8ebcd1a8ba4
# Parent  7932a05741f89e3174ce9a497cbe1cc68ac7aefb
Merge branch 'id-fix-flacky-test' into 'main'

Use require.WithinDuration to fix flaky test

See merge request gitlab-org/gitlab-shell!617

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -196,8 +196,8 @@
 		require.NoError(t, err)
 		require.True(t, token.Valid)
 		require.Equal(t, "gitlab-shell", claims.Issuer)
-		require.Equal(t, time.Now().Truncate(time.Second), claims.IssuedAt.Time, time.Second)
-		require.Equal(t, time.Now().Truncate(time.Second).Add(time.Minute), claims.ExpiresAt.Time, time.Second)
+		require.WithinDuration(t, time.Now().Truncate(time.Second), claims.IssuedAt.Time, time.Second)
+		require.WithinDuration(t, time.Now().Truncate(time.Second).Add(time.Minute), claims.ExpiresAt.Time, time.Second)
 	}
 
 	t.Run("JWT authentication headers for GET", func(t *testing.T) {
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652245010 -14400
#      Wed May 11 08:56:50 2022 +0400
# Node ID d1a10ab09830476c0d088a46a969bced0f1e5127
# Parent  33198b2ae2a7aea3efa4f1940c0dc26af7a2ac39
Release 14.1.1

- Log the error that happens on sconn.Wait() !613

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.1.1
+
+- Log the error that happens on sconn.Wait() !613
+
 v14.1.0
 
 - Make PROXY policy configurable !619
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.1.0
+14.1.1
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652245436 0
#      Wed May 11 05:03:56 2022 +0000
# Node ID 71e6614f1d1c7420c7ac0a17d346b1653d733c63
# Parent  33198b2ae2a7aea3efa4f1940c0dc26af7a2ac39
# Parent  d1a10ab09830476c0d088a46a969bced0f1e5127
Merge branch 'id-release-14-1-0' into 'main'

Release 14.1.1

See merge request gitlab-org/gitlab-shell!621

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.1.1
+
+- Log the error that happens on sconn.Wait() !613
+
 v14.1.0
 
 - Make PROXY policy configurable !619
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.1.0
+14.1.1
# HG changeset patch
# User feistel <6742251-feistel@users.noreply.gitlab.com>
# Date 1651766667 -7200
#      Thu May 05 18:04:27 2022 +0200
# Node ID dc2b81b57713f640435b223bf782e82f04c88ce6
# Parent  fd3b6e6ae179b4abd186a73608301ad17e4ec29c
build: bump go-proxyproto to 0.6.2

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -9,7 +9,7 @@
 	github.com/mattn/go-shellwords v1.0.11
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
-	github.com/pires/go-proxyproto v0.6.1
+	github.com/pires/go-proxyproto v0.6.2
 	github.com/prometheus/client_golang v1.12.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -717,8 +717,8 @@
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
 github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
-github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
-github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
+github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652246590 0
#      Wed May 11 05:23:10 2022 +0000
# Node ID 8a31f4d24683857dd714278ce82ebeb3b14caa0b
# Parent  71e6614f1d1c7420c7ac0a17d346b1653d733c63
# Parent  dc2b81b57713f640435b223bf782e82f04c88ce6
Merge branch 'bump/goproxyproto-062' into 'main'

build: bump go-proxyproto to 0.6.2

See merge request gitlab-org/gitlab-shell!610

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -9,7 +9,7 @@
 	github.com/mattn/go-shellwords v1.0.11
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
-	github.com/pires/go-proxyproto v0.6.1
+	github.com/pires/go-proxyproto v0.6.2
 	github.com/prometheus/client_golang v1.12.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -717,8 +717,8 @@
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
 github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
-github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
-github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
+github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652286357 -14400
#      Wed May 11 20:25:57 2022 +0400
# Node ID 7cb4604a93180774a24f9d00267f49a817543ed7
# Parent  71e6614f1d1c7420c7ac0a17d346b1653d733c63
Implement ClientKeepAlive option

Git clients sometimes open a connection and leave it idling,
like when compressing objects.
Settings like timeout client in HAProxy might cause these
idle connections to be terminated.

Let's send the keepalive message in order to prevent a client
from closing

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -76,6 +76,8 @@
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
+  # Sets an interval after which server will send keepalive message to a client
+  client_alive_interval: 15
   # The server waits for this time (in seconds) for the ongoing connections to complete before shutting down. Defaults to 10.
   grace_period: 10
   # The endpoint that returns 200 OK if the server is ready to receive incoming connections; otherwise, it returns 503 Service Unavailable. Defaults to "/start".
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -22,15 +22,16 @@
 )
 
 type ServerConfig struct {
-	Listen                  string   `yaml:"listen,omitempty"`
-	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
-	ProxyPolicy             string   `yaml:"proxy_policy,omitempty"`
-	WebListen               string   `yaml:"web_listen,omitempty"`
-	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
-	GracePeriodSeconds      uint64   `yaml:"grace_period"`
-	ReadinessProbe          string   `yaml:"readiness_probe"`
-	LivenessProbe           string   `yaml:"liveness_probe"`
-	HostKeyFiles            []string `yaml:"host_key_files,omitempty"`
+	Listen                     string   `yaml:"listen,omitempty"`
+	ProxyProtocol              bool     `yaml:"proxy_protocol,omitempty"`
+	ProxyPolicy                string   `yaml:"proxy_policy,omitempty"`
+	WebListen                  string   `yaml:"web_listen,omitempty"`
+	ConcurrentSessionsLimit    int64    `yaml:"concurrent_sessions_limit,omitempty"`
+	ClientAliveIntervalSeconds int64    `yaml:"client_alive_interval,omitempty"`
+	GracePeriodSeconds         uint64   `yaml:"grace_period"`
+	ReadinessProbe             string   `yaml:"readiness_probe"`
+	LivenessProbe              string   `yaml:"liveness_probe"`
+	HostKeyFiles               []string `yaml:"host_key_files,omitempty"`
 }
 
 type HttpSettingsConfig struct {
@@ -75,12 +76,13 @@
 	}
 
 	DefaultServerConfig = ServerConfig{
-		Listen:                  "[::]:22",
-		WebListen:               "localhost:9122",
-		ConcurrentSessionsLimit: 10,
-		GracePeriodSeconds:      10,
-		ReadinessProbe:          "/start",
-		LivenessProbe:           "/health",
+		Listen:                     "[::]:22",
+		WebListen:                  "localhost:9122",
+		ConcurrentSessionsLimit:    10,
+		GracePeriodSeconds:         10,
+		ClientAliveIntervalSeconds: 15,
+		ReadinessProbe:             "/start",
+		LivenessProbe:              "/health",
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
@@ -89,6 +91,10 @@
 	}
 )
 
+func (sc *ServerConfig) ClientAliveInterval() time.Duration {
+	return time.Duration(sc.ClientAliveIntervalSeconds) * time.Second
+}
+
 func (sc *ServerConfig) GracePeriod() time.Duration {
 	return time.Duration(sc.GracePeriodSeconds) * time.Second
 }
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -7,28 +7,41 @@
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
 
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
+const KeepAliveMsg = "keepalive@openssh.com"
+
 type connection struct {
+	cfg                *config.Config
 	concurrentSessions *semaphore.Weighted
 	remoteAddr         string
+	sconn              *ssh.ServerConn
 }
 
 type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request)
 
-func newConnection(maxSessions int64, remoteAddr string) *connection {
+func newConnection(cfg *config.Config, remoteAddr string, sconn *ssh.ServerConn) *connection {
 	return &connection{
-		concurrentSessions: semaphore.NewWeighted(maxSessions),
+		cfg:                cfg,
+		concurrentSessions: semaphore.NewWeighted(cfg.Server.ConcurrentSessionsLimit),
 		remoteAddr:         remoteAddr,
+		sconn:              sconn,
 	}
 }
 
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
+	if c.cfg.Server.ClientAliveIntervalSeconds > 0 {
+		ticker := time.NewTicker(c.cfg.Server.ClientAliveInterval())
+		defer ticker.Stop()
+		go c.sendKeepAliveMsg(ctx, ticker)
+	}
+
 	for newChannel := range chans {
 		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
@@ -68,3 +81,18 @@
 		}()
 	}
 }
+
+func (c *connection) sendKeepAliveMsg(ctx context.Context, ticker *time.Ticker) {
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-ticker.C:
+			ctxlog.Debug("session: handleShell: send keepalive message to a client")
+
+			c.sconn.SendRequest(KeepAliveMsg, true, nil)
+		}
+	}
+}
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -3,10 +3,14 @@
 import (
 	"context"
 	"errors"
+	"sync"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
 type rejectCall struct {
@@ -47,8 +51,32 @@
 	return f.extraData
 }
 
+type fakeConn struct {
+	ssh.Conn
+
+	sentRequestName string
+	mu              sync.Mutex
+}
+
+func (f *fakeConn) SentRequestName() string {
+	f.mu.Lock()
+	defer f.mu.Unlock()
+
+	return f.sentRequestName
+}
+
+func (f *fakeConn) SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error) {
+	f.mu.Lock()
+	defer f.mu.Unlock()
+
+	f.sentRequestName = name
+
+	return true, nil, nil
+}
+
 func setup(sessionsNum int64, newChannel *fakeNewChannel) (*connection, chan ssh.NewChannel) {
-	conn := newConnection(sessionsNum, "127.0.0.1:50000")
+	cfg := &config.Config{Server: config.ServerConfig{ConcurrentSessionsLimit: sessionsNum, ClientAliveIntervalSeconds: 1}}
+	conn := newConnection(cfg, "127.0.0.1:50000", &ssh.ServerConn{&fakeConn{}, nil})
 
 	chans := make(chan ssh.NewChannel, 1)
 	chans <- newChannel
@@ -145,3 +173,16 @@
 
 	require.False(t, channelHandled)
 }
+
+func TestClientAliveInterval(t *testing.T) {
+	f := &fakeConn{}
+
+	conn := newConnection(&config.Config{}, "127.0.0.1:50000", &ssh.ServerConn{f, nil})
+
+	ticker := time.NewTicker(time.Millisecond)
+	defer ticker.Stop()
+
+	go conn.sendKeepAliveMsg(context.Background(), ticker)
+
+	require.Eventually(t, func() bool { return KeepAliveMsg == f.SentRequestName() }, time.Second, time.Millisecond)
+}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -180,7 +180,7 @@
 
 	started := time.Now()
 	var establishSessionDuration float64
-	conn := newConnection(s.Config.Server.ConcurrentSessionsLimit, remoteAddr)
+	conn := newConnection(s.Config, remoteAddr, sconn)
 	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
 		establishSessionDuration = time.Since(started).Seconds()
 		metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -265,6 +265,7 @@
 	cfg.User = user
 	cfg.Server.Listen = serverUrl
 	cfg.Server.ConcurrentSessionsLimit = 1
+	cfg.Server.ClientAliveIntervalSeconds = 15
 	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
 
 	s, err := NewServer(cfg)
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1652335545 0
#      Thu May 12 06:05:45 2022 +0000
# Node ID 522b7fb78312f3073177147e6a23466b32fb6688
# Parent  8a31f4d24683857dd714278ce82ebeb3b14caa0b
# Parent  7cb4604a93180774a24f9d00267f49a817543ed7
Merge branch 'id-implement-client-keep-alive' into 'main'

Implement ClientKeepAlive option

See merge request gitlab-org/gitlab-shell!622

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -76,6 +76,8 @@
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
+  # Sets an interval after which server will send keepalive message to a client
+  client_alive_interval: 15
   # The server waits for this time (in seconds) for the ongoing connections to complete before shutting down. Defaults to 10.
   grace_period: 10
   # The endpoint that returns 200 OK if the server is ready to receive incoming connections; otherwise, it returns 503 Service Unavailable. Defaults to "/start".
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -22,15 +22,16 @@
 )
 
 type ServerConfig struct {
-	Listen                  string   `yaml:"listen,omitempty"`
-	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
-	ProxyPolicy             string   `yaml:"proxy_policy,omitempty"`
-	WebListen               string   `yaml:"web_listen,omitempty"`
-	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
-	GracePeriodSeconds      uint64   `yaml:"grace_period"`
-	ReadinessProbe          string   `yaml:"readiness_probe"`
-	LivenessProbe           string   `yaml:"liveness_probe"`
-	HostKeyFiles            []string `yaml:"host_key_files,omitempty"`
+	Listen                     string   `yaml:"listen,omitempty"`
+	ProxyProtocol              bool     `yaml:"proxy_protocol,omitempty"`
+	ProxyPolicy                string   `yaml:"proxy_policy,omitempty"`
+	WebListen                  string   `yaml:"web_listen,omitempty"`
+	ConcurrentSessionsLimit    int64    `yaml:"concurrent_sessions_limit,omitempty"`
+	ClientAliveIntervalSeconds int64    `yaml:"client_alive_interval,omitempty"`
+	GracePeriodSeconds         uint64   `yaml:"grace_period"`
+	ReadinessProbe             string   `yaml:"readiness_probe"`
+	LivenessProbe              string   `yaml:"liveness_probe"`
+	HostKeyFiles               []string `yaml:"host_key_files,omitempty"`
 }
 
 type HttpSettingsConfig struct {
@@ -75,12 +76,13 @@
 	}
 
 	DefaultServerConfig = ServerConfig{
-		Listen:                  "[::]:22",
-		WebListen:               "localhost:9122",
-		ConcurrentSessionsLimit: 10,
-		GracePeriodSeconds:      10,
-		ReadinessProbe:          "/start",
-		LivenessProbe:           "/health",
+		Listen:                     "[::]:22",
+		WebListen:                  "localhost:9122",
+		ConcurrentSessionsLimit:    10,
+		GracePeriodSeconds:         10,
+		ClientAliveIntervalSeconds: 15,
+		ReadinessProbe:             "/start",
+		LivenessProbe:              "/health",
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
@@ -89,6 +91,10 @@
 	}
 )
 
+func (sc *ServerConfig) ClientAliveInterval() time.Duration {
+	return time.Duration(sc.ClientAliveIntervalSeconds) * time.Second
+}
+
 func (sc *ServerConfig) GracePeriod() time.Duration {
 	return time.Duration(sc.GracePeriodSeconds) * time.Second
 }
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -7,28 +7,41 @@
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
 
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
+const KeepAliveMsg = "keepalive@openssh.com"
+
 type connection struct {
+	cfg                *config.Config
 	concurrentSessions *semaphore.Weighted
 	remoteAddr         string
+	sconn              *ssh.ServerConn
 }
 
 type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request)
 
-func newConnection(maxSessions int64, remoteAddr string) *connection {
+func newConnection(cfg *config.Config, remoteAddr string, sconn *ssh.ServerConn) *connection {
 	return &connection{
-		concurrentSessions: semaphore.NewWeighted(maxSessions),
+		cfg:                cfg,
+		concurrentSessions: semaphore.NewWeighted(cfg.Server.ConcurrentSessionsLimit),
 		remoteAddr:         remoteAddr,
+		sconn:              sconn,
 	}
 }
 
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
+	if c.cfg.Server.ClientAliveIntervalSeconds > 0 {
+		ticker := time.NewTicker(c.cfg.Server.ClientAliveInterval())
+		defer ticker.Stop()
+		go c.sendKeepAliveMsg(ctx, ticker)
+	}
+
 	for newChannel := range chans {
 		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
@@ -68,3 +81,18 @@
 		}()
 	}
 }
+
+func (c *connection) sendKeepAliveMsg(ctx context.Context, ticker *time.Ticker) {
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-ticker.C:
+			ctxlog.Debug("session: handleShell: send keepalive message to a client")
+
+			c.sconn.SendRequest(KeepAliveMsg, true, nil)
+		}
+	}
+}
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -3,10 +3,14 @@
 import (
 	"context"
 	"errors"
+	"sync"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
 type rejectCall struct {
@@ -47,8 +51,32 @@
 	return f.extraData
 }
 
+type fakeConn struct {
+	ssh.Conn
+
+	sentRequestName string
+	mu              sync.Mutex
+}
+
+func (f *fakeConn) SentRequestName() string {
+	f.mu.Lock()
+	defer f.mu.Unlock()
+
+	return f.sentRequestName
+}
+
+func (f *fakeConn) SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error) {
+	f.mu.Lock()
+	defer f.mu.Unlock()
+
+	f.sentRequestName = name
+
+	return true, nil, nil
+}
+
 func setup(sessionsNum int64, newChannel *fakeNewChannel) (*connection, chan ssh.NewChannel) {
-	conn := newConnection(sessionsNum, "127.0.0.1:50000")
+	cfg := &config.Config{Server: config.ServerConfig{ConcurrentSessionsLimit: sessionsNum, ClientAliveIntervalSeconds: 1}}
+	conn := newConnection(cfg, "127.0.0.1:50000", &ssh.ServerConn{&fakeConn{}, nil})
 
 	chans := make(chan ssh.NewChannel, 1)
 	chans <- newChannel
@@ -145,3 +173,16 @@
 
 	require.False(t, channelHandled)
 }
+
+func TestClientAliveInterval(t *testing.T) {
+	f := &fakeConn{}
+
+	conn := newConnection(&config.Config{}, "127.0.0.1:50000", &ssh.ServerConn{f, nil})
+
+	ticker := time.NewTicker(time.Millisecond)
+	defer ticker.Stop()
+
+	go conn.sendKeepAliveMsg(context.Background(), ticker)
+
+	require.Eventually(t, func() bool { return KeepAliveMsg == f.SentRequestName() }, time.Second, time.Millisecond)
+}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -180,7 +180,7 @@
 
 	started := time.Now()
 	var establishSessionDuration float64
-	conn := newConnection(s.Config.Server.ConcurrentSessionsLimit, remoteAddr)
+	conn := newConnection(s.Config, remoteAddr, sconn)
 	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
 		establishSessionDuration = time.Since(started).Seconds()
 		metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -265,6 +265,7 @@
 	cfg.User = user
 	cfg.Server.Listen = serverUrl
 	cfg.Server.ConcurrentSessionsLimit = 1
+	cfg.Server.ClientAliveIntervalSeconds = 15
 	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
 
 	s, err := NewServer(cfg)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652335626 -14400
#      Thu May 12 10:07:06 2022 +0400
# Node ID aa45eb088179b0016ceef62fee7886b91865d4e5
# Parent  522b7fb78312f3073177147e6a23466b32fb6688
Release 14.2.0

- Implement ClientKeepAlive option
- build: bump go-proxyproto to 0.6.2

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v14.2.0
+
+- Implement ClientKeepAlive option !622
+- build: bump go-proxyproto to 0.6.2 !610
+
 v14.1.1
 
 - Log the error that happens on sconn.Wait() !613
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.1.1
+14.2.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652336122 0
#      Thu May 12 06:15:22 2022 +0000
# Node ID 95f105751282580f695ff4bec8577de261720bbd
# Parent  522b7fb78312f3073177147e6a23466b32fb6688
# Parent  aa45eb088179b0016ceef62fee7886b91865d4e5
Merge branch 'id-release-14-2-0' into 'main'

Release 14.2.0

See merge request gitlab-org/gitlab-shell!623

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v14.2.0
+
+- Implement ClientKeepAlive option !622
+- build: bump go-proxyproto to 0.6.2 !610
+
 v14.1.1
 
 - Log the error that happens on sconn.Wait() !613
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.1.1
+14.2.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1652447867 -7200
#      Fri May 13 15:17:47 2022 +0200
# Branch heptapod
# Node ID ac4c7a46f488458f92bd9357d31b7bd0fe515ca2
# Parent  2ffbe5787a8e76b7856e447ca406889930fe0691
# Parent  d8e5a6bac1b1712956515f079a43e701cd5adb32
Merged upstream v13.24.0 into heptapod branch

This is the version used in GitLab 14.9.4

diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -1,1 +1,4 @@
 * @ashmckenzie @igor.drozdov @nick.thomas @patrickbajao
+
+[Documentation]
+*.md @aqualls
diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.6
+golang 1.17.7
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,11 @@
+v13.24.0
+
+- Upgrade golang to 1.17.7 !576
+- Add more metrics for gitlab-sshd !574
+- Move code guidelines to doc/beginners_guide.md !572
+- Add docs for full feature list !571
+- Add aqualls as codeowner for docs files !573
+
 v13.23.2
 
 - Bump labkit version to 1.12.0 !569
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -4,6 +4,14 @@
 
 What follows is the original README of GitLab Shell
 
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# GitLab Shell
+
 ## GitLab Shell handles git SSH sessions for GitLab
 
 GitLab Shell handles git SSH sessions for GitLab and modifies the list of authorized keys.
@@ -22,6 +30,8 @@
 1. git pull over SSH -> gitlab-shell -> API call to gitlab-rails (Authorization) -> accept or decline -> establish Gitaly session
 1. git push over SSH -> gitlab-shell (git command is not executed yet) -> establish Gitaly session -> (in Gitaly) gitlab-shell pre-receive hook -> API call to gitlab-rails (authorization) -> accept or decline push
 
+[Full feature list](doc/features.md)
+
 ## Code status
 
 [![pipeline status](https://gitlab.com/gitlab-org/gitlab-shell/badges/main/pipeline.svg)](https://gitlab.com/gitlab-org/gitlab-shell/-/pipelines?ref=main)
@@ -38,90 +48,11 @@
 We follow the [Golang Release Policy](https://golang.org/doc/devel/release.html#policy)
 of supporting the current stable version and the previous two major versions.
 
-## Check
-
-Checks if GitLab API access and redis via internal API can be reached:
-
-    make check
-
-## Compile
-
-Builds the `gitlab-shell` binaries, placing them into `bin/`.
-
-    make compile
-
-## Install
-
-Builds the `gitlab-shell` binaries and installs them onto the filesystem. The
-default location is `/usr/local`, but can be controlled by use of the `PREFIX`
-and `DESTDIR` environment variables.
-
-    make install
-
-## Setup
-
-This command is intended for use when installing GitLab from source on a single
-machine. In addition to compiling the gitlab-shell binaries, it ensures that
-various paths on the filesystem exist with the correct permissions. Do not run
-it unless instructed to by your installation method documentation.
-
-    make setup
-
-
-## Testing
-
-Run tests:
-
-    bundle install
-    make test
-
-Run gofmt:
-
-    make verify
-
-Run both test and verify (the default Makefile target):
-
-    bundle install
-    make validate
-
-### Gitaly
-
-Some tests need a Gitaly server. The
-[`docker-compose.yml`](./docker-compose.yml) file will run Gitaly on
-port 8075. To tell the tests where Gitaly is, set
-`GITALY_CONNECTION_INFO`:
-
-    export GITALY_CONNECTION_INFO='{"address": "tcp://localhost:8075", "storage": "default"}'
-    make test
-
-If no `GITALY_CONNECTION_INFO` is set, the test suite will still run, but any
-tests requiring Gitaly will be skipped. They will always run in the CI
-environment.
-
-## Git LFS
-
-Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
-
-## Logging Guidelines
-
-In general, it should be possible to determine the structure, but not content,
-of a gitlab-shell or gitlab-sshd session just from inspecting the logs. Some
-guidelines:
-
-- We use [`gitlab.com/gitlab-org/labkit/log`](https://pkg.go.dev/gitlab.com/gitlab-org/labkit/log)
-  for logging functionality
-- **Always** include a correlation ID
-- Log messages should be invariant and unique. Include accessory information in
-  fields, using `log.WithField`, `log.WithFields`, or `log.WithError`.
-- Log success cases as well as error cases
-- Logging too much is better than not logging enough. If a message seems too
-  verbose, consider reducing the log level before removing the message.
-
 ## Rate Limiting
 
 GitLab Shell performs rate-limiting by user account and project for git operations. GitLab Shell accepts git operation requests and then makes a call to the Rails rate-limiter (backed by Redis). If the `user + project` exceeds the rate limit then GitLab Shell will then drop further connection requests for that `user + project`.
 
-The rate-limiter is applied at the git command (plumbing) level. Each command has a rate limit of 600/minute. For example, `git push` has 600/minute and `git pull` has another 600/minute. 
+The rate-limiter is applied at the git command (plumbing) level. Each command has a rate limit of 600/minute. For example, `git push` has 600/minute and `git pull` has another 600/minute.
 
 Because they are using the same plumbing command `git-upload-pack`, `git pull` and `git clone` are in effect the same command for the purposes of rate-limiting.
 
@@ -133,7 +64,8 @@
 
 ## Contributing
 
-See [CONTRIBUTING.md](./CONTRIBUTING.md).
+- See [CONTRIBUTING.md](./CONTRIBUTING.md).
+- See the [beginner's guide](doc/beginners_guide.md).
 
 ## License
 
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.23.2
+13.24.0
diff --git a/doc/beginners_guide.md b/doc/beginners_guide.md
new file mode 100644
--- /dev/null
+++ b/doc/beginners_guide.md
@@ -0,0 +1,94 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Beginner's guide to GitLab Shell contributions
+
+## Check
+
+Checks if GitLab API access and Redis via internal API can be reached:
+
+```shell
+make check
+```
+
+## Compile
+
+Builds the `gitlab-shell` binaries, placing them into `bin/`.
+
+```shell
+make compile
+```
+
+## Install
+
+Builds the `gitlab-shell` binaries and installs them onto the file system. The
+default location is `/usr/local`, but you can change the location by setting the `PREFIX`
+and `DESTDIR` environment variables.
+
+```shell
+make install
+```
+
+## Setup
+
+This command is intended for use when installing GitLab from source on a single
+machine. It compiles the GitLab Shell binaries, and ensures that
+various paths on the file system exist with the correct permissions. Do not run
+this command unless your installation method documentation instructs you to.
+
+```shell
+make setup
+```
+
+## Testing
+
+Run tests:
+
+```shell
+bundle install
+make test
+```
+
+Run Gofmt:
+
+```shell
+make verify
+```
+
+Run both test and verify (the default Makefile target):
+
+```shell
+bundle install
+make validate
+```
+
+## Gitaly
+
+Some tests need a Gitaly server. The
+[`docker-compose.yml`](../docker-compose.yml) file runs Gitaly on port 8075.
+To tell the tests where Gitaly is, set `GITALY_CONNECTION_INFO`:
+
+```plaintext
+export GITALY_CONNECTION_INFO='{"address": "tcp://localhost:8075", "storage": "default"}'
+make test
+```
+
+If no `GITALY_CONNECTION_INFO` is set, the test suite still runs, but any
+tests requiring Gitaly are skipped. The tests always run in the CI environment.
+
+## Logging Guidelines
+
+In general, you can determine the structure, but not content, of a GitLab Shell
+or `gitlab-sshd` session by inspecting the logs. Some guidelines:
+
+- We use [`gitlab.com/gitlab-org/labkit/log`](https://pkg.go.dev/gitlab.com/gitlab-org/labkit/log)
+  for logging.
+- Always include a correlation ID.
+- Log messages should be invariant and unique. Include accessory information in
+  fields, using `log.WithField`, `log.WithFields`, or `log.WithError`.
+- Log both success cases and error cases.
+- Logging too much is better than not logging enough. If a message seems too
+  verbose, consider reducing the log level before removing the message.
diff --git a/doc/features.md b/doc/features.md
new file mode 100644
--- /dev/null
+++ b/doc/features.md
@@ -0,0 +1,87 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Feature list
+
+## Discover
+
+Allows users to identify themselves on an instance via SSH. The command helps to
+confirm quickly whether a user has SSH access to the instance:
+
+```shell
+ssh git@<hostname>
+
+PTY allocation request failed on channel 0
+Welcome to GitLab, @username!
+Connection to staging.gitlab.com closed.
+```
+
+When permission is denied, it returns:
+
+```shell
+ssh git@<hostname>
+git@<hostname>: Permission denied (publickey).
+```
+
+## Git operations
+
+GitLab Shell provides support for Git operations over SSH by processing
+`git-upload-pack`, `git-receive-pack` and `git-upload-archive` SSH commands.
+It limits the set of commands to predefined Git commands:
+
+- `git archive`
+- `git clone`
+- `git pull`
+- `git push`
+
+## Generate new 2FA recovery codes
+
+Enables users to
+[generate new 2FA recovery codes](https://docs.gitlab.com/ee/user/profile/account/two_factor_authentication.html#generate-new-recovery-codes-using-ssh):
+
+```plaintext
+ssh git@<hostname> 2fa_recovery_codes
+Are you sure you want to generate new two-factor recovery codes?
+Any existing recovery codes you saved will be invalidated. (yes/no)
+yes
+
+Your two-factor authentication recovery codes are:
+...
+```
+
+## Verify 2FA OTP
+
+Allows users to verify their
+[2FA one-time password (OTP)](https://docs.gitlab.com/ee/security/two_factor_authentication.html#2fa-for-git-over-ssh-operations):
+
+```shell
+ssh git@<hostname> 2fa_verify
+OTP: 347419
+
+OTP validation failed.
+```
+
+## LFS authentication
+
+Enables users to generate credentials for LFS authentication:
+
+```shell
+ssh git@<hostname> git-lfs-authenticate <project-path> <upload/download>
+
+{"header":{"Authorization":"Basic ..."},"href":"https://gitlab.com/user/project.git/info/lfs","expires_in":7200}
+```
+
+## Personal access token
+
+Enables users to use personal access tokens via SSH:
+
+```shell
+ssh git@<hostname> personal_access_token <name> <scope1[,scope2,...]> [ttl_days]
+
+Token:   glpat-...
+Scopes:  api
+Expires: 2022-02-05
+```
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -10,7 +10,6 @@
 	"sync"
 	"time"
 
-	"github.com/prometheus/client_golang/prometheus/promhttp"
 	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
@@ -141,7 +140,7 @@
 		}
 
 		tr := client.Transport
-		client.Transport = promhttp.InstrumentRoundTripperDuration(metrics.HttpRequestDuration, tr)
+		client.Transport = metrics.NewRoundTripper(tr)
 
 		c.httpClient = client
 	})
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -25,7 +25,7 @@
 	require.Equal(t, "foo", os.Getenv("SSL_CERT_DIR"))
 }
 
-func TestHttpClient(t *testing.T) {
+func TestCustomPrometheusMetrics(t *testing.T) {
 	url := testserver.StartHttpServer(t, []testserver.TestRequestHandler{})
 
 	config := &Config{GitlabUrl: url}
@@ -38,14 +38,19 @@
 	ms, err := prometheus.DefaultGatherer.Gather()
 	require.NoError(t, err)
 
-	lastMetric := ms[0]
-	require.Equal(t, lastMetric.GetName(), "gitlab_shell_http_request_seconds")
-
-	labels := lastMetric.GetMetric()[0].Label
+	var actualNames []string
+	for _, m := range ms[0:6] {
+		actualNames = append(actualNames, m.GetName())
+	}
 
-	require.Equal(t, "code", labels[0].GetName())
-	require.Equal(t, "404", labels[0].GetValue())
+	expectedMetricNames := []string{
+		"gitlab_shell_http_in_flight_requests",
+		"gitlab_shell_http_request_duration_seconds",
+		"gitlab_shell_http_requests_total",
+		"gitlab_shell_sshd_concurrent_limited_sessions_total",
+		"gitlab_shell_sshd_connection_duration_seconds",
+		"gitlab_shell_sshd_in_flight_connections",
+	}
 
-	require.Equal(t, "method", labels[1].GetName())
-	require.Equal(t, "get", labels[1].GetValue())
+	require.Equal(t, expectedMetricNames, actualNames)
 }
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -1,14 +1,25 @@
 package metrics
 
 import (
+	"net/http"
+
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/client_golang/prometheus/promauto"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
 )
 
 const (
 	namespace     = "gitlab_shell"
 	sshdSubsystem = "sshd"
 	httpSubsystem = "http"
+
+	httpInFlightRequestsMetricName       = "in_flight_requests"
+	httpRequestsTotalMetricName          = "requests_total"
+	httpRequestDurationSecondsMetricName = "request_duration_seconds"
+
+	sshdConnectionsInFlightName = "in_flight_connections"
+	sshdConnectionDuration      = "connection_duration_seconds"
+	sshdHitMaxSessions          = "concurrent_limited_sessions_total"
 )
 
 var (
@@ -16,7 +27,7 @@
 		prometheus.HistogramOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      "connection_duration_seconds",
+			Name:      sshdConnectionDuration,
 			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
 			Buckets: []float64{
 				0.005, /* 5ms */
@@ -32,32 +43,72 @@
 		},
 	)
 
+	SshdConnectionsInFlight = promauto.NewGauge(
+		prometheus.GaugeOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      sshdConnectionsInFlightName,
+			Help:      "A gauge of connections currently being served by gitlab-shell sshd.",
+		},
+	)
+
 	SshdHitMaxSessions = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      "concurrent_limited_sessions_total",
+			Name:      sshdHitMaxSessions,
 			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
 		},
 	)
 
-	HttpRequestDuration = promauto.NewHistogramVec(
+	// The metrics and the buckets size are similar to the ones we have for handlers in Labkit
+	// When the MR: https://gitlab.com/gitlab-org/labkit/-/merge_requests/150 is merged,
+	// these metrics can be refactored out of Gitlab Shell code by using the helper function from Labkit
+	httpRequestsTotal = promauto.NewCounterVec(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: httpSubsystem,
+			Name:      httpRequestsTotalMetricName,
+			Help:      "A counter for http requests.",
+		},
+		[]string{"code", "method"},
+	)
+
+	httpRequestDurationSeconds = promauto.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Namespace: namespace,
 			Subsystem: httpSubsystem,
-			Name:      "request_seconds",
-			Help:      "A histogram of latencies for gitlab-shell http requests",
+			Name:      httpRequestDurationSecondsMetricName,
+			Help:      "A histogram of latencies for http requests.",
 			Buckets: []float64{
-				0.01, /* 10ms */
-				0.05, /* 50ms */
-				0.1,  /* 100ms */
-				0.25, /* 250ms */
-				0.5,  /* 500ms */
-				1.0,  /* 1s */
-				5.0,  /* 5s */
-				10.0, /* 10s */
+				0.005, /* 5ms */
+				0.025, /* 25ms */
+				0.1,   /* 100ms */
+				0.5,   /* 500ms */
+				1.0,   /* 1s */
+				10.0,  /* 10s */
+				30.0,  /* 30s */
+				60.0,  /* 1m */
+				300.0, /* 5m */
 			},
 		},
 		[]string{"code", "method"},
 	)
+
+	httpInFlightRequests = promauto.NewGauge(
+		prometheus.GaugeOpts{
+			Namespace: namespace,
+			Subsystem: httpSubsystem,
+			Name:      httpInFlightRequestsMetricName,
+			Help:      "A gauge of requests currently being performed.",
+		},
+	)
 )
+
+func NewRoundTripper(next http.RoundTripper) promhttp.RoundTripperFunc {
+	rt := next
+
+	rt = promhttp.InstrumentRoundTripperCounter(httpRequestsTotal, rt)
+	rt = promhttp.InstrumentRoundTripperDuration(httpRequestDurationSeconds, rt)
+	return promhttp.InstrumentRoundTripperInFlight(httpInFlightRequests, rt)
+}
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -31,6 +31,9 @@
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
+	metrics.SshdConnectionsInFlight.Inc()
+	defer metrics.SshdConnectionsInFlight.Dec()
+
 	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
 
 	for newChannel := range chans {
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1652447971 -7200
#      Fri May 13 15:19:31 2022 +0200
# Branch heptapod
# Node ID 3eca478bc6dda51a700df01889a4e0fbc0726fd1
# Parent  ac4c7a46f488458f92bd9357d31b7bd0fe515ca2
Updated Heptapod version files

A bit in advance of the release (which will be simply a tag creation).

diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 13.24.0
+
+- Jump to upstream GitLab Shell v13.24.0 (GitLab 14.8 series)
+
 ## 13.23.0
 
 - Jump to upstream GitLab Shell v13.23.2 (GitLab 14.8 series)
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-13.23.0
+13.24.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1652899311 -7200
#      Wed May 18 20:41:51 2022 +0200
# Branch heptapod
# Node ID eaa400f46c97a398037f0a72a5a30836f31a3586
# Parent  3eca478bc6dda51a700df01889a4e0fbc0726fd1
HEPTAPOD_CHANGELOG: fixed typo

diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -12,7 +12,7 @@
 
 ## 13.24.0
 
-- Jump to upstream GitLab Shell v13.24.0 (GitLab 14.8 series)
+- Jump to upstream GitLab Shell v13.24.0 (GitLab 14.9 series)
 
 ## 13.23.0
 
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1652899332 -7200
#      Wed May 18 20:42:12 2022 +0200
# Branch heptapod
# Node ID 0cef84a5b973b0b8be5a3262178120f5d2a35294
# Parent  eaa400f46c97a398037f0a72a5a30836f31a3586
Added tag heptapod-13.24.0 for changeset eaa400f46c97

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -38,3 +38,4 @@
 b15301c706769652286af112a4437dc8cdfac86e heptapod-13.21.0
 7f259a883e58759e4734cb0f38449379bb36aca6 heptapod-13.22.0
 61814961ec7c871e9a081f4399536e5b8a62fabf heptapod-13.23.0
+eaa400f46c97a398037f0a72a5a30836f31a3586 heptapod-13.24.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1653996420 -7200
#      Tue May 31 13:27:00 2022 +0200
# Branch heptapod-stable
# Node ID 3b031eaaaf7b158306134942e70b00abf2182cd0
# Parent  bcbcabd3963c27047fe50a5765ba6992a4449bad
# Parent  0cef84a5b973b0b8be5a3262178120f5d2a35294
heptapod#675: making 0.31 the new stable

diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -1,1 +1,4 @@
 * @ashmckenzie @igor.drozdov @nick.thomas @patrickbajao
+
+[Documentation]
+*.md @aqualls
diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -38,3 +38,4 @@
 b15301c706769652286af112a4437dc8cdfac86e heptapod-13.21.0
 7f259a883e58759e4734cb0f38449379bb36aca6 heptapod-13.22.0
 61814961ec7c871e9a081f4399536e5b8a62fabf heptapod-13.23.0
+eaa400f46c97a398037f0a72a5a30836f31a3586 heptapod-13.24.0
diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.6
+golang 1.17.7
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,11 @@
+v13.24.0
+
+- Upgrade golang to 1.17.7 !576
+- Add more metrics for gitlab-sshd !574
+- Move code guidelines to doc/beginners_guide.md !572
+- Add docs for full feature list !571
+- Add aqualls as codeowner for docs files !573
+
 v13.23.2
 
 - Bump labkit version to 1.12.0 !569
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -1,6 +1,7 @@
 # Changelog for Heptapod Shell
 
-The CHANGELOG file is about the upstream GitLab Shell.
+See the `CHANGELOG` file for information about upstream GitLab Shell
+versions.
 
 ## Versioning policy
 
@@ -9,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 13.24.0
+
+- Jump to upstream GitLab Shell v13.24.0 (GitLab 14.9 series)
+
 ## 13.23.0
 
 - Jump to upstream GitLab Shell v13.23.2 (GitLab 14.8 series)
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-13.23.0
+13.24.0
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -4,6 +4,14 @@
 
 What follows is the original README of GitLab Shell
 
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# GitLab Shell
+
 ## GitLab Shell handles git SSH sessions for GitLab
 
 GitLab Shell handles git SSH sessions for GitLab and modifies the list of authorized keys.
@@ -22,6 +30,8 @@
 1. git pull over SSH -> gitlab-shell -> API call to gitlab-rails (Authorization) -> accept or decline -> establish Gitaly session
 1. git push over SSH -> gitlab-shell (git command is not executed yet) -> establish Gitaly session -> (in Gitaly) gitlab-shell pre-receive hook -> API call to gitlab-rails (authorization) -> accept or decline push
 
+[Full feature list](doc/features.md)
+
 ## Code status
 
 [![pipeline status](https://gitlab.com/gitlab-org/gitlab-shell/badges/main/pipeline.svg)](https://gitlab.com/gitlab-org/gitlab-shell/-/pipelines?ref=main)
@@ -38,90 +48,11 @@
 We follow the [Golang Release Policy](https://golang.org/doc/devel/release.html#policy)
 of supporting the current stable version and the previous two major versions.
 
-## Check
-
-Checks if GitLab API access and redis via internal API can be reached:
-
-    make check
-
-## Compile
-
-Builds the `gitlab-shell` binaries, placing them into `bin/`.
-
-    make compile
-
-## Install
-
-Builds the `gitlab-shell` binaries and installs them onto the filesystem. The
-default location is `/usr/local`, but can be controlled by use of the `PREFIX`
-and `DESTDIR` environment variables.
-
-    make install
-
-## Setup
-
-This command is intended for use when installing GitLab from source on a single
-machine. In addition to compiling the gitlab-shell binaries, it ensures that
-various paths on the filesystem exist with the correct permissions. Do not run
-it unless instructed to by your installation method documentation.
-
-    make setup
-
-
-## Testing
-
-Run tests:
-
-    bundle install
-    make test
-
-Run gofmt:
-
-    make verify
-
-Run both test and verify (the default Makefile target):
-
-    bundle install
-    make validate
-
-### Gitaly
-
-Some tests need a Gitaly server. The
-[`docker-compose.yml`](./docker-compose.yml) file will run Gitaly on
-port 8075. To tell the tests where Gitaly is, set
-`GITALY_CONNECTION_INFO`:
-
-    export GITALY_CONNECTION_INFO='{"address": "tcp://localhost:8075", "storage": "default"}'
-    make test
-
-If no `GITALY_CONNECTION_INFO` is set, the test suite will still run, but any
-tests requiring Gitaly will be skipped. They will always run in the CI
-environment.
-
-## Git LFS
-
-Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
-
-## Logging Guidelines
-
-In general, it should be possible to determine the structure, but not content,
-of a gitlab-shell or gitlab-sshd session just from inspecting the logs. Some
-guidelines:
-
-- We use [`gitlab.com/gitlab-org/labkit/log`](https://pkg.go.dev/gitlab.com/gitlab-org/labkit/log)
-  for logging functionality
-- **Always** include a correlation ID
-- Log messages should be invariant and unique. Include accessory information in
-  fields, using `log.WithField`, `log.WithFields`, or `log.WithError`.
-- Log success cases as well as error cases
-- Logging too much is better than not logging enough. If a message seems too
-  verbose, consider reducing the log level before removing the message.
-
 ## Rate Limiting
 
 GitLab Shell performs rate-limiting by user account and project for git operations. GitLab Shell accepts git operation requests and then makes a call to the Rails rate-limiter (backed by Redis). If the `user + project` exceeds the rate limit then GitLab Shell will then drop further connection requests for that `user + project`.
 
-The rate-limiter is applied at the git command (plumbing) level. Each command has a rate limit of 600/minute. For example, `git push` has 600/minute and `git pull` has another 600/minute. 
+The rate-limiter is applied at the git command (plumbing) level. Each command has a rate limit of 600/minute. For example, `git push` has 600/minute and `git pull` has another 600/minute.
 
 Because they are using the same plumbing command `git-upload-pack`, `git pull` and `git clone` are in effect the same command for the purposes of rate-limiting.
 
@@ -133,7 +64,8 @@
 
 ## Contributing
 
-See [CONTRIBUTING.md](./CONTRIBUTING.md).
+- See [CONTRIBUTING.md](./CONTRIBUTING.md).
+- See the [beginner's guide](doc/beginners_guide.md).
 
 ## License
 
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.23.2
+13.24.0
diff --git a/doc/beginners_guide.md b/doc/beginners_guide.md
new file mode 100644
--- /dev/null
+++ b/doc/beginners_guide.md
@@ -0,0 +1,94 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Beginner's guide to GitLab Shell contributions
+
+## Check
+
+Checks if GitLab API access and Redis via internal API can be reached:
+
+```shell
+make check
+```
+
+## Compile
+
+Builds the `gitlab-shell` binaries, placing them into `bin/`.
+
+```shell
+make compile
+```
+
+## Install
+
+Builds the `gitlab-shell` binaries and installs them onto the file system. The
+default location is `/usr/local`, but you can change the location by setting the `PREFIX`
+and `DESTDIR` environment variables.
+
+```shell
+make install
+```
+
+## Setup
+
+This command is intended for use when installing GitLab from source on a single
+machine. It compiles the GitLab Shell binaries, and ensures that
+various paths on the file system exist with the correct permissions. Do not run
+this command unless your installation method documentation instructs you to.
+
+```shell
+make setup
+```
+
+## Testing
+
+Run tests:
+
+```shell
+bundle install
+make test
+```
+
+Run Gofmt:
+
+```shell
+make verify
+```
+
+Run both test and verify (the default Makefile target):
+
+```shell
+bundle install
+make validate
+```
+
+## Gitaly
+
+Some tests need a Gitaly server. The
+[`docker-compose.yml`](../docker-compose.yml) file runs Gitaly on port 8075.
+To tell the tests where Gitaly is, set `GITALY_CONNECTION_INFO`:
+
+```plaintext
+export GITALY_CONNECTION_INFO='{"address": "tcp://localhost:8075", "storage": "default"}'
+make test
+```
+
+If no `GITALY_CONNECTION_INFO` is set, the test suite still runs, but any
+tests requiring Gitaly are skipped. The tests always run in the CI environment.
+
+## Logging Guidelines
+
+In general, you can determine the structure, but not content, of a GitLab Shell
+or `gitlab-sshd` session by inspecting the logs. Some guidelines:
+
+- We use [`gitlab.com/gitlab-org/labkit/log`](https://pkg.go.dev/gitlab.com/gitlab-org/labkit/log)
+  for logging.
+- Always include a correlation ID.
+- Log messages should be invariant and unique. Include accessory information in
+  fields, using `log.WithField`, `log.WithFields`, or `log.WithError`.
+- Log both success cases and error cases.
+- Logging too much is better than not logging enough. If a message seems too
+  verbose, consider reducing the log level before removing the message.
diff --git a/doc/features.md b/doc/features.md
new file mode 100644
--- /dev/null
+++ b/doc/features.md
@@ -0,0 +1,87 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Feature list
+
+## Discover
+
+Allows users to identify themselves on an instance via SSH. The command helps to
+confirm quickly whether a user has SSH access to the instance:
+
+```shell
+ssh git@<hostname>
+
+PTY allocation request failed on channel 0
+Welcome to GitLab, @username!
+Connection to staging.gitlab.com closed.
+```
+
+When permission is denied, it returns:
+
+```shell
+ssh git@<hostname>
+git@<hostname>: Permission denied (publickey).
+```
+
+## Git operations
+
+GitLab Shell provides support for Git operations over SSH by processing
+`git-upload-pack`, `git-receive-pack` and `git-upload-archive` SSH commands.
+It limits the set of commands to predefined Git commands:
+
+- `git archive`
+- `git clone`
+- `git pull`
+- `git push`
+
+## Generate new 2FA recovery codes
+
+Enables users to
+[generate new 2FA recovery codes](https://docs.gitlab.com/ee/user/profile/account/two_factor_authentication.html#generate-new-recovery-codes-using-ssh):
+
+```plaintext
+ssh git@<hostname> 2fa_recovery_codes
+Are you sure you want to generate new two-factor recovery codes?
+Any existing recovery codes you saved will be invalidated. (yes/no)
+yes
+
+Your two-factor authentication recovery codes are:
+...
+```
+
+## Verify 2FA OTP
+
+Allows users to verify their
+[2FA one-time password (OTP)](https://docs.gitlab.com/ee/security/two_factor_authentication.html#2fa-for-git-over-ssh-operations):
+
+```shell
+ssh git@<hostname> 2fa_verify
+OTP: 347419
+
+OTP validation failed.
+```
+
+## LFS authentication
+
+Enables users to generate credentials for LFS authentication:
+
+```shell
+ssh git@<hostname> git-lfs-authenticate <project-path> <upload/download>
+
+{"header":{"Authorization":"Basic ..."},"href":"https://gitlab.com/user/project.git/info/lfs","expires_in":7200}
+```
+
+## Personal access token
+
+Enables users to use personal access tokens via SSH:
+
+```shell
+ssh git@<hostname> personal_access_token <name> <scope1[,scope2,...]> [ttl_days]
+
+Token:   glpat-...
+Scopes:  api
+Expires: 2022-02-05
+```
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -10,7 +10,6 @@
 	"sync"
 	"time"
 
-	"github.com/prometheus/client_golang/prometheus/promhttp"
 	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
@@ -141,7 +140,7 @@
 		}
 
 		tr := client.Transport
-		client.Transport = promhttp.InstrumentRoundTripperDuration(metrics.HttpRequestDuration, tr)
+		client.Transport = metrics.NewRoundTripper(tr)
 
 		c.httpClient = client
 	})
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -25,7 +25,7 @@
 	require.Equal(t, "foo", os.Getenv("SSL_CERT_DIR"))
 }
 
-func TestHttpClient(t *testing.T) {
+func TestCustomPrometheusMetrics(t *testing.T) {
 	url := testserver.StartHttpServer(t, []testserver.TestRequestHandler{})
 
 	config := &Config{GitlabUrl: url}
@@ -38,14 +38,19 @@
 	ms, err := prometheus.DefaultGatherer.Gather()
 	require.NoError(t, err)
 
-	lastMetric := ms[0]
-	require.Equal(t, lastMetric.GetName(), "gitlab_shell_http_request_seconds")
-
-	labels := lastMetric.GetMetric()[0].Label
+	var actualNames []string
+	for _, m := range ms[0:6] {
+		actualNames = append(actualNames, m.GetName())
+	}
 
-	require.Equal(t, "code", labels[0].GetName())
-	require.Equal(t, "404", labels[0].GetValue())
+	expectedMetricNames := []string{
+		"gitlab_shell_http_in_flight_requests",
+		"gitlab_shell_http_request_duration_seconds",
+		"gitlab_shell_http_requests_total",
+		"gitlab_shell_sshd_concurrent_limited_sessions_total",
+		"gitlab_shell_sshd_connection_duration_seconds",
+		"gitlab_shell_sshd_in_flight_connections",
+	}
 
-	require.Equal(t, "method", labels[1].GetName())
-	require.Equal(t, "get", labels[1].GetValue())
+	require.Equal(t, expectedMetricNames, actualNames)
 }
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -1,14 +1,25 @@
 package metrics
 
 import (
+	"net/http"
+
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/client_golang/prometheus/promauto"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
 )
 
 const (
 	namespace     = "gitlab_shell"
 	sshdSubsystem = "sshd"
 	httpSubsystem = "http"
+
+	httpInFlightRequestsMetricName       = "in_flight_requests"
+	httpRequestsTotalMetricName          = "requests_total"
+	httpRequestDurationSecondsMetricName = "request_duration_seconds"
+
+	sshdConnectionsInFlightName = "in_flight_connections"
+	sshdConnectionDuration      = "connection_duration_seconds"
+	sshdHitMaxSessions          = "concurrent_limited_sessions_total"
 )
 
 var (
@@ -16,7 +27,7 @@
 		prometheus.HistogramOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      "connection_duration_seconds",
+			Name:      sshdConnectionDuration,
 			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
 			Buckets: []float64{
 				0.005, /* 5ms */
@@ -32,32 +43,72 @@
 		},
 	)
 
+	SshdConnectionsInFlight = promauto.NewGauge(
+		prometheus.GaugeOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      sshdConnectionsInFlightName,
+			Help:      "A gauge of connections currently being served by gitlab-shell sshd.",
+		},
+	)
+
 	SshdHitMaxSessions = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      "concurrent_limited_sessions_total",
+			Name:      sshdHitMaxSessions,
 			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
 		},
 	)
 
-	HttpRequestDuration = promauto.NewHistogramVec(
+	// The metrics and the buckets size are similar to the ones we have for handlers in Labkit
+	// When the MR: https://gitlab.com/gitlab-org/labkit/-/merge_requests/150 is merged,
+	// these metrics can be refactored out of Gitlab Shell code by using the helper function from Labkit
+	httpRequestsTotal = promauto.NewCounterVec(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: httpSubsystem,
+			Name:      httpRequestsTotalMetricName,
+			Help:      "A counter for http requests.",
+		},
+		[]string{"code", "method"},
+	)
+
+	httpRequestDurationSeconds = promauto.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Namespace: namespace,
 			Subsystem: httpSubsystem,
-			Name:      "request_seconds",
-			Help:      "A histogram of latencies for gitlab-shell http requests",
+			Name:      httpRequestDurationSecondsMetricName,
+			Help:      "A histogram of latencies for http requests.",
 			Buckets: []float64{
-				0.01, /* 10ms */
-				0.05, /* 50ms */
-				0.1,  /* 100ms */
-				0.25, /* 250ms */
-				0.5,  /* 500ms */
-				1.0,  /* 1s */
-				5.0,  /* 5s */
-				10.0, /* 10s */
+				0.005, /* 5ms */
+				0.025, /* 25ms */
+				0.1,   /* 100ms */
+				0.5,   /* 500ms */
+				1.0,   /* 1s */
+				10.0,  /* 10s */
+				30.0,  /* 30s */
+				60.0,  /* 1m */
+				300.0, /* 5m */
 			},
 		},
 		[]string{"code", "method"},
 	)
+
+	httpInFlightRequests = promauto.NewGauge(
+		prometheus.GaugeOpts{
+			Namespace: namespace,
+			Subsystem: httpSubsystem,
+			Name:      httpInFlightRequestsMetricName,
+			Help:      "A gauge of requests currently being performed.",
+		},
+	)
 )
+
+func NewRoundTripper(next http.RoundTripper) promhttp.RoundTripperFunc {
+	rt := next
+
+	rt = promhttp.InstrumentRoundTripperCounter(httpRequestsTotal, rt)
+	rt = promhttp.InstrumentRoundTripperDuration(httpRequestDurationSeconds, rt)
+	return promhttp.InstrumentRoundTripperInFlight(httpInFlightRequests, rt)
+}
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -31,6 +31,9 @@
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
+	metrics.SshdConnectionsInFlight.Inc()
+	defer metrics.SshdConnectionsInFlight.Dec()
+
 	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
 
 	for newChannel := range chans {
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1654097153 -7200
#      Wed Jun 01 17:25:53 2022 +0200
# Branch heptapod
# Node ID b2dc62c22d14ce793d1e9ddee673350d2ea6d651
# Parent  0cef84a5b973b0b8be5a3262178120f5d2a35294
# Parent  6712c68c42156ec09eefaab72446f498341d0464
Merged upstream v13.25.1 into heptapod branch

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,6 +3,9 @@
   - template: Security/SAST.gitlab-ci.yml
   - template: Security/Dependency-Scanning.gitlab-ci.yml
   - template: Security/Secret-Detection.gitlab-ci.yml
+  - project: 'gitlab-org/quality/pipeline-common'
+    file:
+      - '/ci/danger-review.yml'
 
 variables:
   DOCKER_VERSION: "20.10.3"
@@ -52,23 +55,19 @@
   script:
     - make verify test
 
-go:1.16:
+tests:
   extends: .test
-  image: golang:1.16
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
-go:1.17:
-  extends: .test
-  image: golang:1.17
+  image: golang:${GO_VERSION}
+  parallel:
+    matrix:
+      - GO_VERSION: ["1.16", "1.17", "1.18"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
 
 race:
   extends: .test
-  image: golang:1.17
+  image: golang:1.18
   script:
     - make test_golang_race
 
diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.7
+golang 1.17.8
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,25 @@
+v13.25.1
+
+- Upgrade golang to 1.17.8 !591
+- Add additional metrics to gitlab-sshd !593
+- Add support for FIPS encryption !597
+
+v13.25.0
+
+- Fix connections duration metrics !588
+- ci: start integrating go 1.18 into the CI pipelines !587
+- Abort long-running unauthenticated SSH connections !582
+
+v13.24.2
+
+- Bump gitaly client !584
+
+v13.24.1
+
+- Default to info level for an empty log-level !579
+- Update Gitaly dependency to v14.9.0-rc1 !578
+- Reuse Gitaly connections and sidechannel !575
+
 v13.24.0
 
 - Upgrade golang to 1.17.7 !576
diff --git a/Dangerfile b/Dangerfile
new file mode 100644
--- /dev/null
+++ b/Dangerfile
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'gitlab-dangerfiles'
+
+Gitlab::Dangerfiles.for_project(self) do |gitlab_dangerfiles|
+  gitlab_dangerfiles.import_plugins
+
+  gitlab_dangerfiles.import_dangerfiles(except: %w[changelog commit_messages simple_roulette])
+end
diff --git a/Gemfile b/Gemfile
--- a/Gemfile
+++ b/Gemfile
@@ -3,3 +3,7 @@
 group :development, :test do
   gem 'rspec', '~> 3.8.0'
 end
+
+group :development, :danger do
+  gem 'gitlab-dangerfiles', '~> 3.0.0'
+end
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,7 +1,89 @@
 GEM
   remote: https://rubygems.org/
   specs:
+    addressable (2.8.0)
+      public_suffix (>= 2.0.2, < 5.0)
+    claide (1.1.0)
+    claide-plugins (0.9.2)
+      cork
+      nap
+      open4 (~> 1.3)
+    colored2 (3.1.2)
+    cork (0.3.0)
+      colored2 (~> 3.1)
+    danger (8.5.0)
+      claide (~> 1.0)
+      claide-plugins (>= 0.9.2)
+      colored2 (~> 3.1)
+      cork (~> 0.1)
+      faraday (>= 0.9.0, < 2.0)
+      faraday-http-cache (~> 2.0)
+      git (~> 1.7)
+      kramdown (~> 2.3)
+      kramdown-parser-gfm (~> 1.0)
+      no_proxy_fix
+      octokit (~> 4.7)
+      terminal-table (>= 1, < 4)
+    danger-gitlab (8.0.0)
+      danger
+      gitlab (~> 4.2, >= 4.2.0)
     diff-lcs (1.3)
+    faraday (1.10.0)
+      faraday-em_http (~> 1.0)
+      faraday-em_synchrony (~> 1.0)
+      faraday-excon (~> 1.1)
+      faraday-httpclient (~> 1.0)
+      faraday-multipart (~> 1.0)
+      faraday-net_http (~> 1.0)
+      faraday-net_http_persistent (~> 1.0)
+      faraday-patron (~> 1.0)
+      faraday-rack (~> 1.0)
+      faraday-retry (~> 1.0)
+      ruby2_keywords (>= 0.0.4)
+    faraday-em_http (1.0.0)
+    faraday-em_synchrony (1.0.0)
+    faraday-excon (1.1.0)
+    faraday-http-cache (2.2.0)
+      faraday (>= 0.8)
+    faraday-httpclient (1.0.1)
+    faraday-multipart (1.0.3)
+      multipart-post (>= 1.2, < 3)
+    faraday-net_http (1.0.1)
+    faraday-net_http_persistent (1.2.0)
+    faraday-patron (1.0.0)
+    faraday-rack (1.0.0)
+    faraday-retry (1.0.3)
+    git (1.10.2)
+      rchardet (~> 1.8)
+    gitlab (4.18.0)
+      httparty (~> 0.18)
+      terminal-table (>= 1.5.1)
+    gitlab-dangerfiles (3.0.0)
+      danger (>= 8.4.5)
+      danger-gitlab (>= 8.0.0)
+      rake
+    httparty (0.20.0)
+      mime-types (~> 3.0)
+      multi_xml (>= 0.5.2)
+    kramdown (2.3.2)
+      rexml
+    kramdown-parser-gfm (1.1.0)
+      kramdown (~> 2.0)
+    mime-types (3.4.1)
+      mime-types-data (~> 3.2015)
+    mime-types-data (3.2022.0105)
+    multi_xml (0.6.0)
+    multipart-post (2.1.1)
+    nap (1.1.0)
+    no_proxy_fix (0.1.2)
+    octokit (4.22.0)
+      faraday (>= 0.9)
+      sawyer (~> 0.8.0, >= 0.5.3)
+    open4 (1.3.4)
+    public_suffix (4.0.6)
+    rake (13.0.6)
+    rchardet (1.8.0)
+    rexml (3.2.5)
     rspec (3.8.0)
       rspec-core (~> 3.8.0)
       rspec-expectations (~> 3.8.0)
@@ -15,11 +97,19 @@
       diff-lcs (>= 1.2.0, < 2.0)
       rspec-support (~> 3.8.0)
     rspec-support (3.8.0)
+    ruby2_keywords (0.0.5)
+    sawyer (0.8.2)
+      addressable (>= 2.3.5)
+      faraday (> 0.8, < 2.0)
+    terminal-table (3.0.2)
+      unicode-display_width (>= 1.1.1, < 3)
+    unicode-display_width (2.1.0)
 
 PLATFORMS
   ruby
 
 DEPENDENCIES
+  gitlab-dangerfiles (~> 3.0.0)
   rspec (~> 3.8.0)
 
 BUNDLED WITH
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 13.25.0
+
+- Jump to upstream GitLab Shell v13.25.1 (GitLab 14.10 series)
+
 ## 13.24.0
 
 - Jump to upstream GitLab Shell v13.24.0 (GitLab 14.9 series)
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-13.24.0
+13.25.0
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,14 @@
 .PHONY: validate verify verify_ruby verify_golang test test_ruby test_golang coverage coverage_golang setup _script_install build compile check clean install
 
+FIPS_MODE ?= 0
 GO_SOURCES := $(shell find . -name '*.go')
 VERSION_STRING := $(shell ./compute_version || echo unknown)
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
+
+ifeq (${FIPS_MODE}, 1)
+    BUILD_TAGS += boringcrypto
+endif
+
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)" -mod=mod
 
 PREFIX ?= /usr/local
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.24.0
+13.25.1
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -11,6 +11,7 @@
 	"gitlab.com/gitlab-org/labkit/log"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/boring"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -68,9 +69,12 @@
 	ctx, finished := command.Setup(executable.Name, config)
 	defer finished()
 
+	config.GitalyClient.InitSidechannelRegistry(ctx)
+
 	cmdName := reflect.TypeOf(cmd).String()
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("gitlab-shell: main: executing command")
+	boring.CheckBoring()
 
 	if err := cmd.Execute(ctx); err != nil {
 		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -69,6 +69,8 @@
 	ctx, finished := command.Setup("gitlab-sshd", cfg)
 	defer finished()
 
+	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+
 	server, err := sshd.NewServer(cfg)
 	if err != nil {
 		log.WithError(err).Fatal("Failed to start GitLab built-in sshd")
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -94,6 +94,14 @@
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
+  # The server waits for this time (in seconds) for the ongoing connections to complete before shutting down. Defaults to 10.
+  grace_period: 10
+  # The endpoint that returns 200 OK if the server is ready to receive incoming connections; otherwise, it returns 503 Service Unavailable. Defaults to "/start".
+  readiness_probe: "/start"
+  # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
+  liveness_probe: "/health"
+  # The server disconnects after this time (in seconds) if the user has not successfully logged in. Defaults to 60.
+  login_grace_time: 60
   # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@
 	github.com/pires/go-proxyproto v0.6.1
 	github.com/prometheus/client_golang v1.11.0
 	github.com/stretchr/testify v1.7.0
-	gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490
+	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
 	gitlab.com/gitlab-org/labkit v1.12.0
 	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -149,6 +149,8 @@
 github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
+github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
+github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -583,7 +585,7 @@
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
 github.com/libgit2/git2go/v31 v31.4.12/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec=
-github.com/libgit2/git2go/v32 v32.1.6/go.mod h1:FAA2ePV5PlLjw1ccncFIvu2v8hJSZVN5IzEn4lo/vwo=
+github.com/libgit2/git2go/v33 v33.0.9/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI=
@@ -865,8 +867,8 @@
 gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
 gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
 gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
-gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490 h1:NzcVF6tscgsW890MQfVAhMVnAhXeohPaUGOTuFfIJXs=
-gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490/go.mod h1:taZCYTkDDPh8LCetkdOEx/x+mkvNgGc9GIGlWkXivk0=
+gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059 h1:X7+3GQIxUpScXpIMCU5+sfpYvZyBIQ3GMlEosP7Jssw=
+gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80 h1:rVzTLlEDlX/ocWqNxeuxb9XklLzmI9FdP0teRqkSg00=
diff --git a/internal/boring/boring.go b/internal/boring/boring.go
new file mode 100644
--- /dev/null
+++ b/internal/boring/boring.go
@@ -0,0 +1,23 @@
+//go:build boringcrypto
+// +build boringcrypto
+
+package boring
+
+import (
+	"crypto/boring"
+
+	"gitlab.com/gitlab-org/labkit/log"
+)
+
+// CheckBoring checks whether FIPS crypto has been enabled. For the FIPS Go
+// compiler in https://github.com/golang-fips/go, this requires that:
+//
+// 1. The kernel has FIPS enabled (e.g. `/proc/sys/crypto/fips_enabled` is 1).
+// 2. A system OpenSSL can be dynamically loaded via ldopen().
+func CheckBoring() {
+	if boring.Enabled() {
+		log.Info("FIPS mode is enabled. Using an external SSL library.")
+		return
+	}
+	log.Info("Gitaly was compiled with FIPS mode, but an external SSL library was not enabled.")
+}
diff --git a/internal/boring/notboring.go b/internal/boring/notboring.go
new file mode 100644
--- /dev/null
+++ b/internal/boring/notboring.go
@@ -0,0 +1,9 @@
+//go:build !boringcrypto
+// +build !boringcrypto
+
+package boring
+
+// CheckBoring does nothing when the boringcrypto tag is not in the
+// build.
+func CheckBoring() {
+}
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -13,13 +13,7 @@
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
-	gc := &handler.GitalyCommand{
-		Config:      c.Config,
-		ServiceName: string(commandargs.ReceivePack),
-		Address:     response.Gitaly.Address,
-		Token:       response.Gitaly.Token,
-		Features:    response.Gitaly.Features,
-	}
+	gc := handler.NewGitalyCommand(c.Config, string(commandargs.ReceivePack), response)
 
 	request := &pb.SSHReceivePackRequest{
 		Repository:       &response.Gitaly.Repo,
@@ -30,8 +24,8 @@
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
 		rw := c.ReadWriter
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -13,18 +13,12 @@
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
-	gc := &handler.GitalyCommand{
-		Config:      c.Config,
-		ServiceName: string(commandargs.UploadArchive),
-		Address:     response.Gitaly.Address,
-		Token:       response.Gitaly.Token,
-		Features:    response.Gitaly.Features,
-	}
+	gc := handler.NewGitalyCommand(c.Config, string(commandargs.UploadArchive), response)
 
 	request := &pb.SSHUploadArchiveRequest{Repository: &response.Gitaly.Repo}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
 		rw := c.ReadWriter
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -13,26 +13,20 @@
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
-	gc := &handler.GitalyCommand{
-		Config:      c.Config,
-		ServiceName: string(commandargs.UploadPack),
-		Address:     response.Gitaly.Address,
-		Token:       response.Gitaly.Token,
-		Features:    response.Gitaly.Features,
-	}
+	gc := handler.NewGitalyCommand(c.Config, string(commandargs.UploadPack), response)
 
 	if response.Gitaly.UseSidechannel {
-		gc.DialSidechannel = true
 		request := &pb.SSHUploadPackWithSidechannelRequest{
 			Repository:       &response.Gitaly.Repo,
 			GitProtocol:      c.Args.Env.GitProtocolVersion,
 			GitConfigOptions: response.GitConfigOptions,
 		}
 
-		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-			ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+			ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 			defer cancel()
 
+			registry := c.Config.GitalyClient.SidechannelRegistry
 			rw := c.ReadWriter
 			return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
 		})
@@ -44,8 +38,8 @@
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
 		rw := c.ReadWriter
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -97,15 +97,18 @@
 		Env:         env,
 	}
 
+	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
+	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
+
+	cfg := &config.Config{GitlabUrl: url}
+	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+
 	cmd := &Command{
-		Config:     &config.Config{GitlabUrl: url},
+		Config:     cfg,
 		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
 
-	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
-	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
-
 	err := cmd.Execute(ctx)
 	require.NoError(t, err)
 
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -13,6 +13,7 @@
 	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
 
@@ -31,6 +32,7 @@
 	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
 	WebListen               string   `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
+	LoginGraceTimeSeconds   uint64   `yaml:"login_grace_time"`
 	GracePeriodSeconds      uint64   `yaml:"grace_period"`
 	ReadinessProbe          string   `yaml:"readiness_probe"`
 	LivenessProbe           string   `yaml:"liveness_probe"`
@@ -86,6 +88,8 @@
 	httpClient     *client.HttpClient
 	httpClientErr  error
 	httpClientOnce sync.Once
+
+	GitalyClient gitaly.Client
 }
 
 // The defaults to apply before parsing the config file(s).
@@ -102,6 +106,7 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
+		LoginGraceTimeSeconds:   60,
 		GracePeriodSeconds:      10,
 		ReadinessProbe:          "/start",
 		LivenessProbe:           "/health",
@@ -113,6 +118,10 @@
 	}
 )
 
+func (sc *ServerConfig) LoginGraceTime() time.Duration {
+	return time.Duration(sc.LoginGraceTimeSeconds) * time.Second
+}
+
 func (sc *ServerConfig) GracePeriod() time.Duration {
 	return time.Duration(sc.GracePeriodSeconds) * time.Second
 }
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -39,7 +39,7 @@
 	require.NoError(t, err)
 
 	var actualNames []string
-	for _, m := range ms[0:6] {
+	for _, m := range ms[0:9] {
 		actualNames = append(actualNames, m.GetName())
 	}
 
@@ -48,8 +48,11 @@
 		"gitlab_shell_http_request_duration_seconds",
 		"gitlab_shell_http_requests_total",
 		"gitlab_shell_sshd_concurrent_limited_sessions_total",
-		"gitlab_shell_sshd_connection_duration_seconds",
 		"gitlab_shell_sshd_in_flight_connections",
+		"gitlab_shell_sshd_session_duration_seconds",
+		"gitlab_shell_sshd_session_established_duration_seconds",
+		"gitlab_sli:shell_sshd_sessions:errors_total",
+		"gitlab_sli:shell_sshd_sessions:total",
 	}
 
 	require.Equal(t, expectedMetricNames, actualNames)
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
new file mode 100644
--- /dev/null
+++ b/internal/gitaly/gitaly.go
@@ -0,0 +1,133 @@
+package gitaly
+
+import (
+	"context"
+	"fmt"
+	"sync"
+
+	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
+	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
+	"google.golang.org/grpc"
+
+	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
+	"gitlab.com/gitlab-org/gitaly/v14/client"
+	gitalyclient "gitlab.com/gitlab-org/gitaly/v14/client"
+	"gitlab.com/gitlab-org/labkit/correlation"
+	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
+	"gitlab.com/gitlab-org/labkit/log"
+	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+)
+
+type Command struct {
+	ServiceName     string
+	Address         string
+	Token           string
+	DialSidechannel bool
+}
+
+type connectionsCache struct {
+	sync.RWMutex
+
+	connections map[Command]*grpc.ClientConn
+}
+
+type Client struct {
+	SidechannelRegistry *gitalyclient.SidechannelRegistry
+
+	cache connectionsCache
+}
+
+func (c *Client) InitSidechannelRegistry(ctx context.Context) {
+	c.SidechannelRegistry = gitalyclient.NewSidechannelRegistry(log.ContextLogger(ctx))
+}
+
+func (c *Client) GetConnection(ctx context.Context, cmd Command) (*grpc.ClientConn, error) {
+	c.cache.RLock()
+	conn := c.cache.connections[cmd]
+	c.cache.RUnlock()
+
+	if conn != nil {
+		return conn, nil
+	}
+
+	c.cache.Lock()
+	defer c.cache.Unlock()
+
+	if conn := c.cache.connections[cmd]; conn != nil {
+		return conn, nil
+	}
+
+	conn, err := c.newConnection(ctx, cmd)
+	if err != nil {
+		return nil, err
+	}
+
+	if c.cache.connections == nil {
+		c.cache.connections = make(map[Command]*grpc.ClientConn)
+	}
+
+	c.cache.connections[cmd] = conn
+
+	return conn, nil
+}
+
+func (c *Client) newConnection(ctx context.Context, cmd Command) (conn *grpc.ClientConn, err error) {
+	defer func() {
+		label := "ok"
+		if err != nil {
+			label = "fail"
+		}
+		metrics.GitalyConnectionsTotal.WithLabelValues(label).Inc()
+	}()
+
+	if cmd.Address == "" {
+		return nil, fmt.Errorf("no gitaly_address given")
+	}
+
+	serviceName := correlation.ExtractClientNameFromContext(ctx)
+	if serviceName == "" {
+		serviceName = "gitlab-shell-unknown"
+
+		log.WithContextFields(ctx, log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
+	}
+
+	serviceName = fmt.Sprintf("%s-%s", serviceName, cmd.ServiceName)
+
+	connOpts := client.DefaultDialOpts
+	connOpts = append(
+		connOpts,
+		grpc.WithStreamInterceptor(
+			grpc_middleware.ChainStreamClient(
+				grpctracing.StreamClientTracingInterceptor(),
+				grpc_prometheus.StreamClientInterceptor,
+				grpccorrelation.StreamClientCorrelationInterceptor(
+					grpccorrelation.WithClientName(serviceName),
+				),
+			),
+		),
+
+		grpc.WithUnaryInterceptor(
+			grpc_middleware.ChainUnaryClient(
+				grpctracing.UnaryClientTracingInterceptor(),
+				grpc_prometheus.UnaryClientInterceptor,
+				grpccorrelation.UnaryClientCorrelationInterceptor(
+					grpccorrelation.WithClientName(serviceName),
+				),
+			),
+		),
+	)
+
+	if cmd.Token != "" {
+		connOpts = append(connOpts,
+			grpc.WithPerRPCCredentials(gitalyauth.RPCCredentialsV2(cmd.Token)),
+		)
+	}
+
+	if cmd.DialSidechannel {
+		return client.DialSidechannel(ctx, cmd.Address, c.SidechannelRegistry, connOpts)
+	}
+
+	return client.DialContext(ctx, cmd.Address, connOpts)
+}
diff --git a/internal/gitaly/gitaly_test.go b/internal/gitaly/gitaly_test.go
new file mode 100644
--- /dev/null
+++ b/internal/gitaly/gitaly_test.go
@@ -0,0 +1,53 @@
+package gitaly
+
+import (
+	"context"
+	"testing"
+
+	"github.com/prometheus/client_golang/prometheus/testutil"
+	"github.com/stretchr/testify/require"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+)
+
+func TestPrometheusMetrics(t *testing.T) {
+	metrics.GitalyConnectionsTotal.Reset()
+
+	c := &Client{}
+
+	cmd := Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9999"}
+	c.newConnection(context.Background(), cmd)
+	c.newConnection(context.Background(), cmd)
+
+	require.Equal(t, 1, testutil.CollectAndCount(metrics.GitalyConnectionsTotal))
+	require.InDelta(t, 2, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("ok")), 0.1)
+	require.InDelta(t, 0, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("fail")), 0.1)
+
+	cmd = Command{Address: ""}
+	c.newConnection(context.Background(), cmd)
+
+	require.InDelta(t, 2, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("ok")), 0.1)
+	require.InDelta(t, 1, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("fail")), 0.1)
+}
+
+func TestCachedConnections(t *testing.T) {
+	c := &Client{}
+
+	require.Len(t, c.cache.connections, 0)
+
+	cmd := Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9999"}
+
+	conn, err := c.GetConnection(context.Background(), cmd)
+	require.NoError(t, err)
+	require.Len(t, c.cache.connections, 1)
+
+	newConn, err := c.GetConnection(context.Background(), cmd)
+	require.NoError(t, err)
+	require.Len(t, c.cache.connections, 1)
+	require.Equal(t, conn, newConn)
+
+	cmd = Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9998"}
+	_, err = c.GetConnection(context.Background(), cmd)
+	require.NoError(t, err)
+	require.Len(t, c.cache.connections, 2)
+}
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -6,56 +6,57 @@
 	"strconv"
 	"strings"
 
-	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
-	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
 	"google.golang.org/grpc"
 	grpccodes "google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 
-	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
-	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/labkit/correlation"
-	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
 	"gitlab.com/gitlab-org/labkit/log"
-	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
 )
 
 // GitalyHandlerFunc implementations are responsible for making
 // an appropriate Gitaly call using the provided client and context
 // and returning an error from the Gitaly call.
-type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error)
+type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn) (int32, error)
 
 type GitalyCommand struct {
-	Config          *config.Config
-	ServiceName     string
-	Address         string
-	Token           string
-	Features        map[string]string
-	DialSidechannel bool
+	Config   *config.Config
+	Response *accessverifier.Response
+	Command  gitaly.Command
+}
+
+func NewGitalyCommand(cfg *config.Config, serviceName string, response *accessverifier.Response) *GitalyCommand {
+	gc := gitaly.Command{
+		ServiceName:     serviceName,
+		Address:         response.Gitaly.Address,
+		Token:           response.Gitaly.Token,
+		DialSidechannel: response.Gitaly.UseSidechannel,
+	}
+
+	return &GitalyCommand{Config: cfg, Response: response, Command: gc}
 }
 
 // RunGitalyCommand provides a bootstrap for Gitaly commands executed
 // through GitLab-Shell. It ensures that logging, tracing and other
 // common concerns are configured before executing the `handler`.
 func (gc *GitalyCommand) RunGitalyCommand(ctx context.Context, handler GitalyHandlerFunc) error {
-	registry := client.NewSidechannelRegistry(log.ContextLogger(ctx))
-	conn, err := getConn(ctx, gc, registry)
+	// We leave the connection open for future reuse
+	conn, err := gc.getConn(ctx)
 	if err != nil {
 		log.ContextLogger(ctx).WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Failed to get connection to execute Git command")
 
 		return err
 	}
-	defer conn.Close()
 
-	childCtx := withOutgoingMetadata(ctx, gc.Features)
+	childCtx := withOutgoingMetadata(ctx, gc.Response.Gitaly.Features)
 	ctxlog := log.ContextLogger(childCtx)
-	exitStatus, err := handler(childCtx, conn, registry)
+	exitStatus, err := handler(childCtx, conn)
 
 	if err != nil {
 		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
@@ -72,35 +73,35 @@
 
 // PrepareContext wraps a given context with a correlation ID and logs the command to
 // be run.
-func (gc *GitalyCommand) PrepareContext(ctx context.Context, repository *pb.Repository, response *accessverifier.Response, env sshenv.Env) (context.Context, context.CancelFunc) {
+func (gc *GitalyCommand) PrepareContext(ctx context.Context, repository *pb.Repository, env sshenv.Env) (context.Context, context.CancelFunc) {
 	ctx, cancel := context.WithCancel(ctx)
-	gc.LogExecution(ctx, repository, response, env)
+	gc.LogExecution(ctx, repository, env)
 
 	md, ok := metadata.FromOutgoingContext(ctx)
 	if !ok {
 		md = metadata.New(nil)
 	}
-	md.Append("key_id", strconv.Itoa(response.KeyId))
-	md.Append("key_type", response.KeyType)
-	md.Append("user_id", response.UserId)
-	md.Append("username", response.Username)
+	md.Append("key_id", strconv.Itoa(gc.Response.KeyId))
+	md.Append("key_type", gc.Response.KeyType)
+	md.Append("user_id", gc.Response.UserId)
+	md.Append("username", gc.Response.Username)
 	md.Append("remote_ip", env.RemoteAddr)
 	ctx = metadata.NewOutgoingContext(ctx, md)
 
 	return ctx, cancel
 }
 
-func (gc *GitalyCommand) LogExecution(ctx context.Context, repository *pb.Repository, response *accessverifier.Response, env sshenv.Env) {
+func (gc *GitalyCommand) LogExecution(ctx context.Context, repository *pb.Repository, env sshenv.Env) {
 	fields := log.Fields{
-		"command":         gc.ServiceName,
+		"command":         gc.Command.ServiceName,
 		"gl_project_path": repository.GlProjectPath,
 		"gl_repository":   repository.GlRepository,
-		"user_id":         response.UserId,
-		"username":        response.Username,
+		"user_id":         gc.Response.UserId,
+		"username":        gc.Response.Username,
 		"git_protocol":    env.GitProtocolVersion,
 		"remote_ip":       env.RemoteAddr,
-		"gl_key_type":     response.KeyType,
-		"gl_key_id":       response.KeyId,
+		"gl_key_type":     gc.Response.KeyType,
+		"gl_key_id":       gc.Response.KeyId,
 	}
 
 	log.WithContextFields(ctx, fields).Info("executing git command")
@@ -118,53 +119,6 @@
 	return metadata.NewOutgoingContext(ctx, md)
 }
 
-func getConn(ctx context.Context, gc *GitalyCommand, registry *client.SidechannelRegistry) (*grpc.ClientConn, error) {
-	if gc.Address == "" {
-		return nil, fmt.Errorf("no gitaly_address given")
-	}
-
-	serviceName := correlation.ExtractClientNameFromContext(ctx)
-	if serviceName == "" {
-		serviceName = "gitlab-shell-unknown"
-
-		log.WithContextFields(ctx, log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
-	}
-
-	serviceName = fmt.Sprintf("%s-%s", serviceName, gc.ServiceName)
-
-	connOpts := client.DefaultDialOpts
-	connOpts = append(
-		connOpts,
-		grpc.WithStreamInterceptor(
-			grpc_middleware.ChainStreamClient(
-				grpctracing.StreamClientTracingInterceptor(),
-				grpc_prometheus.StreamClientInterceptor,
-				grpccorrelation.StreamClientCorrelationInterceptor(
-					grpccorrelation.WithClientName(serviceName),
-				),
-			),
-		),
-
-		grpc.WithUnaryInterceptor(
-			grpc_middleware.ChainUnaryClient(
-				grpctracing.UnaryClientTracingInterceptor(),
-				grpc_prometheus.UnaryClientInterceptor,
-				grpccorrelation.UnaryClientCorrelationInterceptor(
-					grpccorrelation.WithClientName(serviceName),
-				),
-			),
-		),
-	)
-
-	if gc.Token != "" {
-		connOpts = append(connOpts,
-			grpc.WithPerRPCCredentials(gitalyauth.RPCCredentialsV2(gc.Token)),
-		)
-	}
-
-	if gc.DialSidechannel {
-		return client.DialSidechannel(ctx, gc.Address, registry, connOpts)
-	}
-
-	return client.DialContext(ctx, gc.Address, connOpts)
+func (gc *GitalyCommand) getConn(ctx context.Context) (*grpc.ClientConn, error) {
+	return gc.Config.GitalyClient.GetConnection(ctx, gc.Command)
 }
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -11,15 +11,15 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
-func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn, *client.SidechannelRegistry) (int32, error) {
-	return func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
+func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn) (int32, error) {
+	return func(ctx context.Context, client *grpc.ClientConn) (int32, error) {
 		require.NotNil(t, ctx)
 		require.NotNil(t, client)
 
@@ -28,10 +28,13 @@
 }
 
 func TestRunGitalyCommand(t *testing.T) {
-	cmd := GitalyCommand{
-		Config:  &config.Config{},
-		Address: "tcp://localhost:9999",
-	}
+	cmd := NewGitalyCommand(
+		&config.Config{},
+		string(commandargs.UploadPack),
+		&accessverifier.Response{
+			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
+		},
+	)
 
 	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, nil))
 	require.NoError(t, err)
@@ -41,6 +44,32 @@
 	require.Equal(t, err, expectedErr)
 }
 
+func TestCachingOfGitalyConnections(t *testing.T) {
+	ctx := context.Background()
+	cfg := &config.Config{}
+	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+	response := &accessverifier.Response{
+		Username: "user",
+		Gitaly: accessverifier.Gitaly{
+			Address:        "tcp://localhost:9999",
+			Token:          "token",
+			UseSidechannel: true,
+		},
+	}
+
+	cmd := NewGitalyCommand(cfg, string(commandargs.UploadPack), response)
+
+	conn, err := cmd.getConn(ctx)
+	require.NoError(t, err)
+
+	// Reuses connection for different users
+	response.Username = "another-user"
+	cmd = NewGitalyCommand(cfg, string(commandargs.UploadPack), response)
+	newConn, err := cmd.getConn(ctx)
+	require.NoError(t, err)
+	require.Equal(t, conn, newConn)
+}
+
 func TestMissingGitalyAddress(t *testing.T) {
 	cmd := GitalyCommand{Config: &config.Config{}}
 
@@ -49,10 +78,13 @@
 }
 
 func TestUnavailableGitalyErr(t *testing.T) {
-	cmd := GitalyCommand{
-		Config:  &config.Config{},
-		Address: "tcp://localhost:9999",
-	}
+	cmd := NewGitalyCommand(
+		&config.Config{},
+		string(commandargs.UploadPack),
+		&accessverifier.Response{
+			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
+		},
+	)
 
 	expectedErr := grpcstatus.Error(grpccodes.Unavailable, "error")
 	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, expectedErr))
@@ -68,15 +100,20 @@
 	}{
 		{
 			name: "gitaly_feature_flags",
-			gc: &GitalyCommand{
-				Config:  &config.Config{},
-				Address: "tcp://localhost:9999",
-				Features: map[string]string{
-					"gitaly-feature-cache_invalidator":        "true",
-					"other-ff":                                "true",
-					"gitaly-feature-inforef_uploadpack_cache": "false",
+			gc: NewGitalyCommand(
+				&config.Config{},
+				string(commandargs.UploadPack),
+				&accessverifier.Response{
+					Gitaly: accessverifier.Gitaly{
+						Address: "tcp://localhost:9999",
+						Features: map[string]string{
+							"gitaly-feature-cache_invalidator":        "true",
+							"other-ff":                                "true",
+							"gitaly-feature-inforef_uploadpack_cache": "false",
+						},
+					},
 				},
-			},
+			),
 			want: map[string]string{
 				"gitaly-feature-cache_invalidator":        "true",
 				"gitaly-feature-inforef_uploadpack_cache": "false",
@@ -87,7 +124,7 @@
 		t.Run(tt.name, func(t *testing.T) {
 			cmd := tt.gc
 
-			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn, _ *client.SidechannelRegistry) (int32, error) {
+			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn) (int32, error) {
 				md, exists := metadata.FromOutgoingContext(ctx)
 				require.True(t, exists)
 				require.Equal(t, len(tt.want), md.Len())
@@ -117,10 +154,19 @@
 	}{
 		{
 			name: "client_identity",
-			gc: &GitalyCommand{
-				Config:  &config.Config{},
-				Address: "tcp://localhost:9999",
-			},
+			gc: NewGitalyCommand(
+				&config.Config{},
+				string(commandargs.UploadPack),
+				&accessverifier.Response{
+					KeyId:    1,
+					KeyType:  "key",
+					UserId:   "6",
+					Username: "jane.doe",
+					Gitaly: accessverifier.Gitaly{
+						Address: "tcp://localhost:9999",
+					},
+				},
+			),
 			env: sshenv.Env{
 				GitProtocolVersion: "protocol",
 				IsSSHConnection:    true,
@@ -134,12 +180,6 @@
 				GlRepository:                  "project-26",
 				GlProjectPath:                 "group/private",
 			},
-			response: &accessverifier.Response{
-				KeyId:    1,
-				KeyType:  "key",
-				UserId:   "6",
-				Username: "jane.doe",
-			},
 			want: map[string]string{
 				"key_id":    "1",
 				"key_type":  "key",
@@ -153,7 +193,7 @@
 		t.Run(tt.name, func(t *testing.T) {
 			ctx := context.Background()
 
-			ctx, cancel := tt.gc.PrepareContext(ctx, tt.repo, tt.response, tt.env)
+			ctx, cancel := tt.gc.PrepareContext(ctx, tt.repo, tt.env)
 			defer cancel()
 
 			md, exists := metadata.FromOutgoingContext(ctx)
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -22,6 +22,14 @@
 	return inFmt
 }
 
+func logLevel(inLevel string) string {
+	if inLevel == "" {
+		return "info"
+	}
+
+	return inLevel
+}
+
 func logFile(inFile string) string {
 	if inFile == "" {
 		return "stderr"
@@ -35,7 +43,7 @@
 		log.WithFormatter(logFmt(cfg.LogFormat)),
 		log.WithOutputName(logFile(cfg.LogFile)),
 		log.WithTimezone(time.UTC),
-		log.WithLogLevel(cfg.LogLevel),
+		log.WithLogLevel(logLevel(cfg.LogLevel)),
 	}
 }
 
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -30,9 +30,11 @@
 	tmpFile.Close()
 
 	data, err := os.ReadFile(tmpFile.Name())
+	dataStr := string(data)
 	require.NoError(t, err)
-	require.Contains(t, string(data), `msg":"this is a test"`)
-	require.NotContains(t, string(data), `msg:":"debug log message"`)
+	require.Contains(t, dataStr, `"msg":"this is a test"`)
+	require.NotContains(t, dataStr, `"msg":"debug log message"`)
+	require.NotContains(t, dataStr, `"msg":"unknown log level`)
 }
 
 func TestConfigureWithDebugLogLevel(t *testing.T) {
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -9,36 +9,51 @@
 )
 
 const (
-	namespace     = "gitlab_shell"
-	sshdSubsystem = "sshd"
-	httpSubsystem = "http"
+	namespace       = "gitlab_shell"
+	sshdSubsystem   = "sshd"
+	httpSubsystem   = "http"
+	gitalySubsystem = "gitaly"
 
 	httpInFlightRequestsMetricName       = "in_flight_requests"
 	httpRequestsTotalMetricName          = "requests_total"
 	httpRequestDurationSecondsMetricName = "request_duration_seconds"
 
-	sshdConnectionsInFlightName = "in_flight_connections"
-	sshdConnectionDuration      = "connection_duration_seconds"
-	sshdHitMaxSessions          = "concurrent_limited_sessions_total"
+	sshdConnectionsInFlightName               = "in_flight_connections"
+	sshdHitMaxSessionsName                    = "concurrent_limited_sessions_total"
+	sshdSessionDurationSecondsName            = "session_duration_seconds"
+	sshdSessionEstablishedDurationSecondsName = "session_established_duration_seconds"
+
+	sliSshdSessionsTotalName       = "gitlab_sli:shell_sshd_sessions:total"
+	sliSshdSessionsErrorsTotalName = "gitlab_sli:shell_sshd_sessions:errors_total"
+
+	gitalyConnectionsTotalName = "connections_total"
 )
 
 var (
-	SshdConnectionDuration = promauto.NewHistogram(
+	SshdSessionDuration = promauto.NewHistogram(
 		prometheus.HistogramOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      sshdConnectionDuration,
+			Name:      sshdSessionDurationSecondsName,
 			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
 			Buckets: []float64{
-				0.005, /* 5ms */
-				0.025, /* 25ms */
-				0.1,   /* 100ms */
-				0.5,   /* 500ms */
-				1.0,   /* 1s */
-				10.0,  /* 10s */
-				30.0,  /* 30s */
-				60.0,  /* 1m */
-				300.0, /* 5m */
+				5.0,  /* 5s */
+				30.0, /* 30s */
+				60.0, /* 1m */
+			},
+		},
+	)
+
+	SshdSessionEstablishedDuration = promauto.NewHistogram(
+		prometheus.HistogramOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      sshdSessionEstablishedDurationSecondsName,
+			Help:      "A histogram of latencies until session established to gitlab-shell sshd.",
+			Buckets: []float64{
+				0.5, /* 5ms */
+				1.0, /* 1s */
+				5.0, /* 5s */
 			},
 		},
 	)
@@ -56,11 +71,35 @@
 		prometheus.CounterOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      sshdHitMaxSessions,
+			Name:      sshdHitMaxSessionsName,
 			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
 		},
 	)
 
+	SliSshdSessionsTotal = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Name: sliSshdSessionsTotalName,
+			Help: "Number of SSH sessions that have been established",
+		},
+	)
+
+	SliSshdSessionsErrorsTotal = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Name: sliSshdSessionsErrorsTotalName,
+			Help: "Number of SSH sessions that have failed",
+		},
+	)
+
+	GitalyConnectionsTotal = promauto.NewCounterVec(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: gitalySubsystem,
+			Name:      gitalyConnectionsTotalName,
+			Help:      "Number of Gitaly connections that have been established",
+		},
+		[]string{"status"},
+	)
+
 	// The metrics and the buckets size are similar to the ones we have for handlers in Labkit
 	// When the MR: https://gitlab.com/gitlab-org/labkit/-/merge_requests/150 is merged,
 	// these metrics can be refactored out of Gitlab Shell code by using the helper function from Labkit
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -2,7 +2,6 @@
 
 import (
 	"context"
-	"time"
 
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
@@ -13,7 +12,6 @@
 )
 
 type connection struct {
-	begin              time.Time
 	concurrentSessions *semaphore.Weighted
 	remoteAddr         string
 }
@@ -22,7 +20,6 @@
 
 func newConnection(maxSessions int64, remoteAddr string) *connection {
 	return &connection{
-		begin:              time.Now(),
 		concurrentSessions: semaphore.NewWeighted(maxSessions),
 		remoteAddr:         remoteAddr,
 	}
@@ -31,11 +28,6 @@
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
-	metrics.SshdConnectionsInFlight.Inc()
-	defer metrics.SshdConnectionsInFlight.Dec()
-
-	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
-
 	for newChannel := range chans {
 		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -22,6 +22,7 @@
 	channel     ssh.Channel
 	gitlabKeyId string
 	remoteAddr  string
+	success     bool
 
 	// State managed by the session
 	execCmd            string
@@ -182,6 +183,8 @@
 	log.WithContextFields(ctx, log.Fields{"exit_status": status}).Info("session: exit: exiting")
 	req := exitStatusReq{ExitStatus: status}
 
+	s.success = status == 0
+
 	s.channel.CloseWrite()
 	s.channel.SendRequest("exit-status", false, ssh.Marshal(req))
 }
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -99,6 +99,7 @@
 		expectedExecCmd    string
 		sentRequestName    string
 		sentRequestPayload []byte
+		success            bool
 	}{
 		{
 			desc:            "invalid payload",
@@ -111,6 +112,7 @@
 			expectedExecCmd:    "discover",
 			sentRequestName:    "exit-status",
 			sentRequestPayload: ssh.Marshal(exitStatusReq{ExitStatus: 0}),
+			success:            true,
 		},
 	}
 
@@ -130,6 +132,7 @@
 			require.Equal(t, false, s.handleExec(context.Background(), r))
 			require.Equal(t, tc.sentRequestName, f.sentRequestName)
 			require.Equal(t, tc.sentRequestPayload, f.sentRequestPayload)
+			require.Equal(t, tc.success, s.success)
 		})
 	}
 }
@@ -141,6 +144,7 @@
 		errMsg           string
 		gitlabKeyId      string
 		expectedExitCode uint32
+		success          bool
 	}{
 		{
 			desc:             "fails to parse command",
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -12,6 +12,7 @@
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/log"
@@ -145,6 +146,20 @@
 }
 
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
+	success := false
+
+	metrics.SshdConnectionsInFlight.Inc()
+	started := time.Now()
+	defer func() {
+		metrics.SshdConnectionsInFlight.Dec()
+		metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
+
+		metrics.SliSshdSessionsTotal.Inc()
+		if !success {
+			metrics.SliSshdSessionsErrorsTotal.Inc()
+		}
+	}()
+
 	remoteAddr := nconn.RemoteAddr().String()
 
 	defer s.wg.Done()
@@ -164,7 +179,7 @@
 
 	ctxlog.Info("server: handleConn: start")
 
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
+	sconn, chans, reqs, err := s.initSSHConnection(ctx, nconn)
 	if err != nil {
 		ctxlog.WithError(err).Error("server: handleConn: failed to initialize SSH connection")
 		return
@@ -172,8 +187,12 @@
 
 	go ssh.DiscardRequests(reqs)
 
+	var establishSessionDuration float64
 	conn := newConnection(s.Config.Server.ConcurrentSessionsLimit, remoteAddr)
 	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
+		establishSessionDuration = time.Since(started).Seconds()
+		metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
+
 		session := &session{
 			cfg:         s.Config,
 			channel:     channel,
@@ -182,9 +201,28 @@
 		}
 
 		session.handle(ctx, requests)
+
+		success = session.success
 	})
 
-	ctxlog.Info("server: handleConn: done")
+	ctxlog.WithFields(log.Fields{
+		"duration_s":                   time.Since(started).Seconds(),
+		"establish_session_duration_s": establishSessionDuration,
+	}).Info("server: handleConn: done")
+}
+
+func (s *Server) initSSHConnection(ctx context.Context, nconn net.Conn) (sconn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request, err error) {
+	timer := time.AfterFunc(s.Config.Server.LoginGraceTime(), func() {
+		nconn.Close()
+	})
+	defer func() {
+		// If time.Stop() equals false, that means that AfterFunc has been executed
+		if !timer.Stop() {
+			err = fmt.Errorf("initSSHConnection: ssh handshake timeout of %v", s.Config.Server.LoginGraceTime())
+		}
+	}()
+
+	return ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 }
 
 func unconditionalRequirePolicy(_ net.Addr) (proxyproto.Policy, error) {
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -3,6 +3,7 @@
 import (
 	"context"
 	"fmt"
+	"net"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -134,6 +135,33 @@
 	require.Nil(t, s.Shutdown())
 }
 
+func TestLoginGraceTime(t *testing.T) {
+	s := setupServer(t)
+
+	unauthenticatedRequestStatus := make(chan string)
+	completed := make(chan bool)
+
+	clientCfg := clientConfig(t)
+	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
+		unauthenticatedRequestStatus <- "authentication-started"
+		<-completed // Wait infinitely
+
+		return nil
+	}
+
+	go func() {
+		// Start an SSH connection that never ends
+		ssh.Dial("tcp", serverUrl, clientCfg)
+	}()
+
+	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
+
+	// Verify that the server shutdowns instantly without waiting for any connection to execute
+	// That means that the server doesn't have any connections to wait for
+	require.NoError(t, s.Shutdown())
+	verifyStatus(t, s, StatusClosed)
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
@@ -183,6 +211,7 @@
 	cfg.User = user
 	cfg.Server.Listen = serverUrl
 	cfg.Server.ConcurrentSessionsLimit = 1
+	cfg.Server.LoginGraceTimeSeconds = 1
 	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
 
 	s, err := NewServer(cfg)
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1654097568 -7200
#      Wed Jun 01 17:32:48 2022 +0200
# Branch heptapod
# Node ID e35ff18d0d7035d511f8454909ca5ccc10bbb3fd
# Parent  b2dc62c22d14ce793d1e9ddee673350d2ea6d651
Heptapod CI: resync with upstream

- using `matrix`
- 'race' tests run on Go 1.18

diff --git a/heptapod-ci.yml b/heptapod-ci.yml
--- a/heptapod-ci.yml
+++ b/heptapod-ci.yml
@@ -38,22 +38,18 @@
   script:
     - make verify test
 
-go:1.16:
+tests:
   extends: .test
-  image: golang:1.16
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
-go:1.17:
-  extends: .test
-  image: golang:1.17
+  image: golang:${GO_VERSION}
+  parallel:
+    matrix:
+      - GO_VERSION: ["1.16", "1.17", "1.18"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
 
 race:
   extends: .test
-  image: golang:1.17
+  image: golang:1.18
   script:
     - make test_golang_race
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1654097653 -7200
#      Wed Jun 01 17:34:13 2022 +0200
# Branch heptapod
# Node ID bbf4eff5db1a60372d6a9828b15464d5207c1fbf
# Parent  e35ff18d0d7035d511f8454909ca5ccc10bbb3fd
Heptapod CI: bumped Gitaly version

Upstream just uses "master", but they are by definition working on
a version closer to the bleeding edge than we are.

diff --git a/heptapod-ci.yml b/heptapod-ci.yml
--- a/heptapod-ci.yml
+++ b/heptapod-ci.yml
@@ -31,7 +31,7 @@
     - go version
     - which go
   services:
-    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:v14.5.2
+    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:v14.10.1
       # Disable the hooks so we don't have to stub the GitLab API
       command: ["bash", "-c", "mkdir -p /home/git/repositories && rm -rf /srv/gitlab-shell/hooks/* && exec /usr/bin/env GITALY_TESTING_NO_GIT_HOOKS=1 /scripts/process-wrapper"]
       alias: gitaly
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1655107575 -7200
#      Mon Jun 13 10:06:15 2022 +0200
# Branch heptapod
# Node ID 57a0260a6fb85a23d5d983b8bf04030cc1b79768
# Parent  bbf4eff5db1a60372d6a9828b15464d5207c1fbf
Added tag heptapod-13.25.0 for changeset bbf4eff5db1a

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -39,3 +39,4 @@
 7f259a883e58759e4734cb0f38449379bb36aca6 heptapod-13.22.0
 61814961ec7c871e9a081f4399536e5b8a62fabf heptapod-13.23.0
 eaa400f46c97a398037f0a72a5a30836f31a3586 heptapod-13.24.0
+bbf4eff5db1a60372d6a9828b15464d5207c1fbf heptapod-13.25.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1657204193 -7200
#      Thu Jul 07 16:29:53 2022 +0200
# Branch heptapod-stable
# Node ID 2ab8e6717d3b7798734e1bac9d079c41f52d5b0a
# Parent  3b031eaaaf7b158306134942e70b00abf2182cd0
# Parent  57a0260a6fb85a23d5d983b8bf04030cc1b79768
heptapod#685: making 0.32 the new stable

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,6 +3,9 @@
   - template: Security/SAST.gitlab-ci.yml
   - template: Security/Dependency-Scanning.gitlab-ci.yml
   - template: Security/Secret-Detection.gitlab-ci.yml
+  - project: 'gitlab-org/quality/pipeline-common'
+    file:
+      - '/ci/danger-review.yml'
 
 variables:
   DOCKER_VERSION: "20.10.3"
@@ -52,23 +55,19 @@
   script:
     - make verify test
 
-go:1.16:
+tests:
   extends: .test
-  image: golang:1.16
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
-go:1.17:
-  extends: .test
-  image: golang:1.17
+  image: golang:${GO_VERSION}
+  parallel:
+    matrix:
+      - GO_VERSION: ["1.16", "1.17", "1.18"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
 
 race:
   extends: .test
-  image: golang:1.17
+  image: golang:1.18
   script:
     - make test_golang_race
 
diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -39,3 +39,4 @@
 7f259a883e58759e4734cb0f38449379bb36aca6 heptapod-13.22.0
 61814961ec7c871e9a081f4399536e5b8a62fabf heptapod-13.23.0
 eaa400f46c97a398037f0a72a5a30836f31a3586 heptapod-13.24.0
+bbf4eff5db1a60372d6a9828b15464d5207c1fbf heptapod-13.25.0
diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.7
+golang 1.17.8
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,25 @@
+v13.25.1
+
+- Upgrade golang to 1.17.8 !591
+- Add additional metrics to gitlab-sshd !593
+- Add support for FIPS encryption !597
+
+v13.25.0
+
+- Fix connections duration metrics !588
+- ci: start integrating go 1.18 into the CI pipelines !587
+- Abort long-running unauthenticated SSH connections !582
+
+v13.24.2
+
+- Bump gitaly client !584
+
+v13.24.1
+
+- Default to info level for an empty log-level !579
+- Update Gitaly dependency to v14.9.0-rc1 !578
+- Reuse Gitaly connections and sidechannel !575
+
 v13.24.0
 
 - Upgrade golang to 1.17.7 !576
diff --git a/Dangerfile b/Dangerfile
new file mode 100644
--- /dev/null
+++ b/Dangerfile
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'gitlab-dangerfiles'
+
+Gitlab::Dangerfiles.for_project(self) do |gitlab_dangerfiles|
+  gitlab_dangerfiles.import_plugins
+
+  gitlab_dangerfiles.import_dangerfiles(except: %w[changelog commit_messages simple_roulette])
+end
diff --git a/Gemfile b/Gemfile
--- a/Gemfile
+++ b/Gemfile
@@ -3,3 +3,7 @@
 group :development, :test do
   gem 'rspec', '~> 3.8.0'
 end
+
+group :development, :danger do
+  gem 'gitlab-dangerfiles', '~> 3.0.0'
+end
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,7 +1,89 @@
 GEM
   remote: https://rubygems.org/
   specs:
+    addressable (2.8.0)
+      public_suffix (>= 2.0.2, < 5.0)
+    claide (1.1.0)
+    claide-plugins (0.9.2)
+      cork
+      nap
+      open4 (~> 1.3)
+    colored2 (3.1.2)
+    cork (0.3.0)
+      colored2 (~> 3.1)
+    danger (8.5.0)
+      claide (~> 1.0)
+      claide-plugins (>= 0.9.2)
+      colored2 (~> 3.1)
+      cork (~> 0.1)
+      faraday (>= 0.9.0, < 2.0)
+      faraday-http-cache (~> 2.0)
+      git (~> 1.7)
+      kramdown (~> 2.3)
+      kramdown-parser-gfm (~> 1.0)
+      no_proxy_fix
+      octokit (~> 4.7)
+      terminal-table (>= 1, < 4)
+    danger-gitlab (8.0.0)
+      danger
+      gitlab (~> 4.2, >= 4.2.0)
     diff-lcs (1.3)
+    faraday (1.10.0)
+      faraday-em_http (~> 1.0)
+      faraday-em_synchrony (~> 1.0)
+      faraday-excon (~> 1.1)
+      faraday-httpclient (~> 1.0)
+      faraday-multipart (~> 1.0)
+      faraday-net_http (~> 1.0)
+      faraday-net_http_persistent (~> 1.0)
+      faraday-patron (~> 1.0)
+      faraday-rack (~> 1.0)
+      faraday-retry (~> 1.0)
+      ruby2_keywords (>= 0.0.4)
+    faraday-em_http (1.0.0)
+    faraday-em_synchrony (1.0.0)
+    faraday-excon (1.1.0)
+    faraday-http-cache (2.2.0)
+      faraday (>= 0.8)
+    faraday-httpclient (1.0.1)
+    faraday-multipart (1.0.3)
+      multipart-post (>= 1.2, < 3)
+    faraday-net_http (1.0.1)
+    faraday-net_http_persistent (1.2.0)
+    faraday-patron (1.0.0)
+    faraday-rack (1.0.0)
+    faraday-retry (1.0.3)
+    git (1.10.2)
+      rchardet (~> 1.8)
+    gitlab (4.18.0)
+      httparty (~> 0.18)
+      terminal-table (>= 1.5.1)
+    gitlab-dangerfiles (3.0.0)
+      danger (>= 8.4.5)
+      danger-gitlab (>= 8.0.0)
+      rake
+    httparty (0.20.0)
+      mime-types (~> 3.0)
+      multi_xml (>= 0.5.2)
+    kramdown (2.3.2)
+      rexml
+    kramdown-parser-gfm (1.1.0)
+      kramdown (~> 2.0)
+    mime-types (3.4.1)
+      mime-types-data (~> 3.2015)
+    mime-types-data (3.2022.0105)
+    multi_xml (0.6.0)
+    multipart-post (2.1.1)
+    nap (1.1.0)
+    no_proxy_fix (0.1.2)
+    octokit (4.22.0)
+      faraday (>= 0.9)
+      sawyer (~> 0.8.0, >= 0.5.3)
+    open4 (1.3.4)
+    public_suffix (4.0.6)
+    rake (13.0.6)
+    rchardet (1.8.0)
+    rexml (3.2.5)
     rspec (3.8.0)
       rspec-core (~> 3.8.0)
       rspec-expectations (~> 3.8.0)
@@ -15,11 +97,19 @@
       diff-lcs (>= 1.2.0, < 2.0)
       rspec-support (~> 3.8.0)
     rspec-support (3.8.0)
+    ruby2_keywords (0.0.5)
+    sawyer (0.8.2)
+      addressable (>= 2.3.5)
+      faraday (> 0.8, < 2.0)
+    terminal-table (3.0.2)
+      unicode-display_width (>= 1.1.1, < 3)
+    unicode-display_width (2.1.0)
 
 PLATFORMS
   ruby
 
 DEPENDENCIES
+  gitlab-dangerfiles (~> 3.0.0)
   rspec (~> 3.8.0)
 
 BUNDLED WITH
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 13.25.0
+
+- Jump to upstream GitLab Shell v13.25.1 (GitLab 14.10 series)
+
 ## 13.24.0
 
 - Jump to upstream GitLab Shell v13.24.0 (GitLab 14.9 series)
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-13.24.0
+13.25.0
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,14 @@
 .PHONY: validate verify verify_ruby verify_golang test test_ruby test_golang coverage coverage_golang setup _script_install build compile check clean install
 
+FIPS_MODE ?= 0
 GO_SOURCES := $(shell find . -name '*.go')
 VERSION_STRING := $(shell ./compute_version || echo unknown)
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
+
+ifeq (${FIPS_MODE}, 1)
+    BUILD_TAGS += boringcrypto
+endif
+
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)" -mod=mod
 
 PREFIX ?= /usr/local
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.24.0
+13.25.1
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -11,6 +11,7 @@
 	"gitlab.com/gitlab-org/labkit/log"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/boring"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -68,9 +69,12 @@
 	ctx, finished := command.Setup(executable.Name, config)
 	defer finished()
 
+	config.GitalyClient.InitSidechannelRegistry(ctx)
+
 	cmdName := reflect.TypeOf(cmd).String()
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("gitlab-shell: main: executing command")
+	boring.CheckBoring()
 
 	if err := cmd.Execute(ctx); err != nil {
 		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -69,6 +69,8 @@
 	ctx, finished := command.Setup("gitlab-sshd", cfg)
 	defer finished()
 
+	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+
 	server, err := sshd.NewServer(cfg)
 	if err != nil {
 		log.WithError(err).Fatal("Failed to start GitLab built-in sshd")
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -94,6 +94,14 @@
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
+  # The server waits for this time (in seconds) for the ongoing connections to complete before shutting down. Defaults to 10.
+  grace_period: 10
+  # The endpoint that returns 200 OK if the server is ready to receive incoming connections; otherwise, it returns 503 Service Unavailable. Defaults to "/start".
+  readiness_probe: "/start"
+  # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
+  liveness_probe: "/health"
+  # The server disconnects after this time (in seconds) if the user has not successfully logged in. Defaults to 60.
+  login_grace_time: 60
   # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@
 	github.com/pires/go-proxyproto v0.6.1
 	github.com/prometheus/client_golang v1.11.0
 	github.com/stretchr/testify v1.7.0
-	gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490
+	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
 	gitlab.com/gitlab-org/labkit v1.12.0
 	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -149,6 +149,8 @@
 github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
+github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
+github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -583,7 +585,7 @@
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
 github.com/libgit2/git2go/v31 v31.4.12/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec=
-github.com/libgit2/git2go/v32 v32.1.6/go.mod h1:FAA2ePV5PlLjw1ccncFIvu2v8hJSZVN5IzEn4lo/vwo=
+github.com/libgit2/git2go/v33 v33.0.9/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI=
@@ -865,8 +867,8 @@
 gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
 gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
 gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
-gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490 h1:NzcVF6tscgsW890MQfVAhMVnAhXeohPaUGOTuFfIJXs=
-gitlab.com/gitlab-org/gitaly/v14 v14.6.0-rc1.0.20220121102056-2e398afa0490/go.mod h1:taZCYTkDDPh8LCetkdOEx/x+mkvNgGc9GIGlWkXivk0=
+gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059 h1:X7+3GQIxUpScXpIMCU5+sfpYvZyBIQ3GMlEosP7Jssw=
+gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80 h1:rVzTLlEDlX/ocWqNxeuxb9XklLzmI9FdP0teRqkSg00=
diff --git a/heptapod-ci.yml b/heptapod-ci.yml
--- a/heptapod-ci.yml
+++ b/heptapod-ci.yml
@@ -31,29 +31,25 @@
     - go version
     - which go
   services:
-    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:v14.5.2
+    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:v14.10.1
       # Disable the hooks so we don't have to stub the GitLab API
       command: ["bash", "-c", "mkdir -p /home/git/repositories && rm -rf /srv/gitlab-shell/hooks/* && exec /usr/bin/env GITALY_TESTING_NO_GIT_HOOKS=1 /scripts/process-wrapper"]
       alias: gitaly
   script:
     - make verify test
 
-go:1.16:
+tests:
   extends: .test
-  image: golang:1.16
-  after_script:
-    - make coverage
-  coverage: '/\d+.\d+%/'
-
-go:1.17:
-  extends: .test
-  image: golang:1.17
+  image: golang:${GO_VERSION}
+  parallel:
+    matrix:
+      - GO_VERSION: ["1.16", "1.17", "1.18"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
 
 race:
   extends: .test
-  image: golang:1.17
+  image: golang:1.18
   script:
     - make test_golang_race
diff --git a/internal/boring/boring.go b/internal/boring/boring.go
new file mode 100644
--- /dev/null
+++ b/internal/boring/boring.go
@@ -0,0 +1,23 @@
+//go:build boringcrypto
+// +build boringcrypto
+
+package boring
+
+import (
+	"crypto/boring"
+
+	"gitlab.com/gitlab-org/labkit/log"
+)
+
+// CheckBoring checks whether FIPS crypto has been enabled. For the FIPS Go
+// compiler in https://github.com/golang-fips/go, this requires that:
+//
+// 1. The kernel has FIPS enabled (e.g. `/proc/sys/crypto/fips_enabled` is 1).
+// 2. A system OpenSSL can be dynamically loaded via ldopen().
+func CheckBoring() {
+	if boring.Enabled() {
+		log.Info("FIPS mode is enabled. Using an external SSL library.")
+		return
+	}
+	log.Info("Gitaly was compiled with FIPS mode, but an external SSL library was not enabled.")
+}
diff --git a/internal/boring/notboring.go b/internal/boring/notboring.go
new file mode 100644
--- /dev/null
+++ b/internal/boring/notboring.go
@@ -0,0 +1,9 @@
+//go:build !boringcrypto
+// +build !boringcrypto
+
+package boring
+
+// CheckBoring does nothing when the boringcrypto tag is not in the
+// build.
+func CheckBoring() {
+}
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -13,13 +13,7 @@
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
-	gc := &handler.GitalyCommand{
-		Config:      c.Config,
-		ServiceName: string(commandargs.ReceivePack),
-		Address:     response.Gitaly.Address,
-		Token:       response.Gitaly.Token,
-		Features:    response.Gitaly.Features,
-	}
+	gc := handler.NewGitalyCommand(c.Config, string(commandargs.ReceivePack), response)
 
 	request := &pb.SSHReceivePackRequest{
 		Repository:       &response.Gitaly.Repo,
@@ -30,8 +24,8 @@
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
 		rw := c.ReadWriter
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -13,18 +13,12 @@
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
-	gc := &handler.GitalyCommand{
-		Config:      c.Config,
-		ServiceName: string(commandargs.UploadArchive),
-		Address:     response.Gitaly.Address,
-		Token:       response.Gitaly.Token,
-		Features:    response.Gitaly.Features,
-	}
+	gc := handler.NewGitalyCommand(c.Config, string(commandargs.UploadArchive), response)
 
 	request := &pb.SSHUploadArchiveRequest{Repository: &response.Gitaly.Repo}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
 		rw := c.ReadWriter
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -13,26 +13,20 @@
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
-	gc := &handler.GitalyCommand{
-		Config:      c.Config,
-		ServiceName: string(commandargs.UploadPack),
-		Address:     response.Gitaly.Address,
-		Token:       response.Gitaly.Token,
-		Features:    response.Gitaly.Features,
-	}
+	gc := handler.NewGitalyCommand(c.Config, string(commandargs.UploadPack), response)
 
 	if response.Gitaly.UseSidechannel {
-		gc.DialSidechannel = true
 		request := &pb.SSHUploadPackWithSidechannelRequest{
 			Repository:       &response.Gitaly.Repo,
 			GitProtocol:      c.Args.Env.GitProtocolVersion,
 			GitConfigOptions: response.GitConfigOptions,
 		}
 
-		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-			ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+			ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 			defer cancel()
 
+			registry := c.Config.GitalyClient.SidechannelRegistry
 			rw := c.ReadWriter
 			return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
 		})
@@ -44,8 +38,8 @@
 		GitConfigOptions: response.GitConfigOptions,
 	}
 
-	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
-		ctx, cancel := gc.PrepareContext(ctx, request.Repository, response, c.Args.Env)
+	return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
+		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
 		rw := c.ReadWriter
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -97,15 +97,18 @@
 		Env:         env,
 	}
 
+	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
+	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
+
+	cfg := &config.Config{GitlabUrl: url}
+	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+
 	cmd := &Command{
-		Config:     &config.Config{GitlabUrl: url},
+		Config:     cfg,
 		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
 
-	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
-	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
-
 	err := cmd.Execute(ctx)
 	require.NoError(t, err)
 
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -13,6 +13,7 @@
 	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
 
@@ -31,6 +32,7 @@
 	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
 	WebListen               string   `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
+	LoginGraceTimeSeconds   uint64   `yaml:"login_grace_time"`
 	GracePeriodSeconds      uint64   `yaml:"grace_period"`
 	ReadinessProbe          string   `yaml:"readiness_probe"`
 	LivenessProbe           string   `yaml:"liveness_probe"`
@@ -86,6 +88,8 @@
 	httpClient     *client.HttpClient
 	httpClientErr  error
 	httpClientOnce sync.Once
+
+	GitalyClient gitaly.Client
 }
 
 // The defaults to apply before parsing the config file(s).
@@ -102,6 +106,7 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
+		LoginGraceTimeSeconds:   60,
 		GracePeriodSeconds:      10,
 		ReadinessProbe:          "/start",
 		LivenessProbe:           "/health",
@@ -113,6 +118,10 @@
 	}
 )
 
+func (sc *ServerConfig) LoginGraceTime() time.Duration {
+	return time.Duration(sc.LoginGraceTimeSeconds) * time.Second
+}
+
 func (sc *ServerConfig) GracePeriod() time.Duration {
 	return time.Duration(sc.GracePeriodSeconds) * time.Second
 }
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -39,7 +39,7 @@
 	require.NoError(t, err)
 
 	var actualNames []string
-	for _, m := range ms[0:6] {
+	for _, m := range ms[0:9] {
 		actualNames = append(actualNames, m.GetName())
 	}
 
@@ -48,8 +48,11 @@
 		"gitlab_shell_http_request_duration_seconds",
 		"gitlab_shell_http_requests_total",
 		"gitlab_shell_sshd_concurrent_limited_sessions_total",
-		"gitlab_shell_sshd_connection_duration_seconds",
 		"gitlab_shell_sshd_in_flight_connections",
+		"gitlab_shell_sshd_session_duration_seconds",
+		"gitlab_shell_sshd_session_established_duration_seconds",
+		"gitlab_sli:shell_sshd_sessions:errors_total",
+		"gitlab_sli:shell_sshd_sessions:total",
 	}
 
 	require.Equal(t, expectedMetricNames, actualNames)
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
new file mode 100644
--- /dev/null
+++ b/internal/gitaly/gitaly.go
@@ -0,0 +1,133 @@
+package gitaly
+
+import (
+	"context"
+	"fmt"
+	"sync"
+
+	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
+	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
+	"google.golang.org/grpc"
+
+	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
+	"gitlab.com/gitlab-org/gitaly/v14/client"
+	gitalyclient "gitlab.com/gitlab-org/gitaly/v14/client"
+	"gitlab.com/gitlab-org/labkit/correlation"
+	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
+	"gitlab.com/gitlab-org/labkit/log"
+	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+)
+
+type Command struct {
+	ServiceName     string
+	Address         string
+	Token           string
+	DialSidechannel bool
+}
+
+type connectionsCache struct {
+	sync.RWMutex
+
+	connections map[Command]*grpc.ClientConn
+}
+
+type Client struct {
+	SidechannelRegistry *gitalyclient.SidechannelRegistry
+
+	cache connectionsCache
+}
+
+func (c *Client) InitSidechannelRegistry(ctx context.Context) {
+	c.SidechannelRegistry = gitalyclient.NewSidechannelRegistry(log.ContextLogger(ctx))
+}
+
+func (c *Client) GetConnection(ctx context.Context, cmd Command) (*grpc.ClientConn, error) {
+	c.cache.RLock()
+	conn := c.cache.connections[cmd]
+	c.cache.RUnlock()
+
+	if conn != nil {
+		return conn, nil
+	}
+
+	c.cache.Lock()
+	defer c.cache.Unlock()
+
+	if conn := c.cache.connections[cmd]; conn != nil {
+		return conn, nil
+	}
+
+	conn, err := c.newConnection(ctx, cmd)
+	if err != nil {
+		return nil, err
+	}
+
+	if c.cache.connections == nil {
+		c.cache.connections = make(map[Command]*grpc.ClientConn)
+	}
+
+	c.cache.connections[cmd] = conn
+
+	return conn, nil
+}
+
+func (c *Client) newConnection(ctx context.Context, cmd Command) (conn *grpc.ClientConn, err error) {
+	defer func() {
+		label := "ok"
+		if err != nil {
+			label = "fail"
+		}
+		metrics.GitalyConnectionsTotal.WithLabelValues(label).Inc()
+	}()
+
+	if cmd.Address == "" {
+		return nil, fmt.Errorf("no gitaly_address given")
+	}
+
+	serviceName := correlation.ExtractClientNameFromContext(ctx)
+	if serviceName == "" {
+		serviceName = "gitlab-shell-unknown"
+
+		log.WithContextFields(ctx, log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
+	}
+
+	serviceName = fmt.Sprintf("%s-%s", serviceName, cmd.ServiceName)
+
+	connOpts := client.DefaultDialOpts
+	connOpts = append(
+		connOpts,
+		grpc.WithStreamInterceptor(
+			grpc_middleware.ChainStreamClient(
+				grpctracing.StreamClientTracingInterceptor(),
+				grpc_prometheus.StreamClientInterceptor,
+				grpccorrelation.StreamClientCorrelationInterceptor(
+					grpccorrelation.WithClientName(serviceName),
+				),
+			),
+		),
+
+		grpc.WithUnaryInterceptor(
+			grpc_middleware.ChainUnaryClient(
+				grpctracing.UnaryClientTracingInterceptor(),
+				grpc_prometheus.UnaryClientInterceptor,
+				grpccorrelation.UnaryClientCorrelationInterceptor(
+					grpccorrelation.WithClientName(serviceName),
+				),
+			),
+		),
+	)
+
+	if cmd.Token != "" {
+		connOpts = append(connOpts,
+			grpc.WithPerRPCCredentials(gitalyauth.RPCCredentialsV2(cmd.Token)),
+		)
+	}
+
+	if cmd.DialSidechannel {
+		return client.DialSidechannel(ctx, cmd.Address, c.SidechannelRegistry, connOpts)
+	}
+
+	return client.DialContext(ctx, cmd.Address, connOpts)
+}
diff --git a/internal/gitaly/gitaly_test.go b/internal/gitaly/gitaly_test.go
new file mode 100644
--- /dev/null
+++ b/internal/gitaly/gitaly_test.go
@@ -0,0 +1,53 @@
+package gitaly
+
+import (
+	"context"
+	"testing"
+
+	"github.com/prometheus/client_golang/prometheus/testutil"
+	"github.com/stretchr/testify/require"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+)
+
+func TestPrometheusMetrics(t *testing.T) {
+	metrics.GitalyConnectionsTotal.Reset()
+
+	c := &Client{}
+
+	cmd := Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9999"}
+	c.newConnection(context.Background(), cmd)
+	c.newConnection(context.Background(), cmd)
+
+	require.Equal(t, 1, testutil.CollectAndCount(metrics.GitalyConnectionsTotal))
+	require.InDelta(t, 2, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("ok")), 0.1)
+	require.InDelta(t, 0, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("fail")), 0.1)
+
+	cmd = Command{Address: ""}
+	c.newConnection(context.Background(), cmd)
+
+	require.InDelta(t, 2, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("ok")), 0.1)
+	require.InDelta(t, 1, testutil.ToFloat64(metrics.GitalyConnectionsTotal.WithLabelValues("fail")), 0.1)
+}
+
+func TestCachedConnections(t *testing.T) {
+	c := &Client{}
+
+	require.Len(t, c.cache.connections, 0)
+
+	cmd := Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9999"}
+
+	conn, err := c.GetConnection(context.Background(), cmd)
+	require.NoError(t, err)
+	require.Len(t, c.cache.connections, 1)
+
+	newConn, err := c.GetConnection(context.Background(), cmd)
+	require.NoError(t, err)
+	require.Len(t, c.cache.connections, 1)
+	require.Equal(t, conn, newConn)
+
+	cmd = Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9998"}
+	_, err = c.GetConnection(context.Background(), cmd)
+	require.NoError(t, err)
+	require.Len(t, c.cache.connections, 2)
+}
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -6,56 +6,57 @@
 	"strconv"
 	"strings"
 
-	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
-	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
 	"google.golang.org/grpc"
 	grpccodes "google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 
-	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
-	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/labkit/correlation"
-	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
 	"gitlab.com/gitlab-org/labkit/log"
-	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
 )
 
 // GitalyHandlerFunc implementations are responsible for making
 // an appropriate Gitaly call using the provided client and context
 // and returning an error from the Gitaly call.
-type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error)
+type GitalyHandlerFunc func(ctx context.Context, client *grpc.ClientConn) (int32, error)
 
 type GitalyCommand struct {
-	Config          *config.Config
-	ServiceName     string
-	Address         string
-	Token           string
-	Features        map[string]string
-	DialSidechannel bool
+	Config   *config.Config
+	Response *accessverifier.Response
+	Command  gitaly.Command
+}
+
+func NewGitalyCommand(cfg *config.Config, serviceName string, response *accessverifier.Response) *GitalyCommand {
+	gc := gitaly.Command{
+		ServiceName:     serviceName,
+		Address:         response.Gitaly.Address,
+		Token:           response.Gitaly.Token,
+		DialSidechannel: response.Gitaly.UseSidechannel,
+	}
+
+	return &GitalyCommand{Config: cfg, Response: response, Command: gc}
 }
 
 // RunGitalyCommand provides a bootstrap for Gitaly commands executed
 // through GitLab-Shell. It ensures that logging, tracing and other
 // common concerns are configured before executing the `handler`.
 func (gc *GitalyCommand) RunGitalyCommand(ctx context.Context, handler GitalyHandlerFunc) error {
-	registry := client.NewSidechannelRegistry(log.ContextLogger(ctx))
-	conn, err := getConn(ctx, gc, registry)
+	// We leave the connection open for future reuse
+	conn, err := gc.getConn(ctx)
 	if err != nil {
 		log.ContextLogger(ctx).WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Failed to get connection to execute Git command")
 
 		return err
 	}
-	defer conn.Close()
 
-	childCtx := withOutgoingMetadata(ctx, gc.Features)
+	childCtx := withOutgoingMetadata(ctx, gc.Response.Gitaly.Features)
 	ctxlog := log.ContextLogger(childCtx)
-	exitStatus, err := handler(childCtx, conn, registry)
+	exitStatus, err := handler(childCtx, conn)
 
 	if err != nil {
 		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
@@ -72,35 +73,35 @@
 
 // PrepareContext wraps a given context with a correlation ID and logs the command to
 // be run.
-func (gc *GitalyCommand) PrepareContext(ctx context.Context, repository *pb.Repository, response *accessverifier.Response, env sshenv.Env) (context.Context, context.CancelFunc) {
+func (gc *GitalyCommand) PrepareContext(ctx context.Context, repository *pb.Repository, env sshenv.Env) (context.Context, context.CancelFunc) {
 	ctx, cancel := context.WithCancel(ctx)
-	gc.LogExecution(ctx, repository, response, env)
+	gc.LogExecution(ctx, repository, env)
 
 	md, ok := metadata.FromOutgoingContext(ctx)
 	if !ok {
 		md = metadata.New(nil)
 	}
-	md.Append("key_id", strconv.Itoa(response.KeyId))
-	md.Append("key_type", response.KeyType)
-	md.Append("user_id", response.UserId)
-	md.Append("username", response.Username)
+	md.Append("key_id", strconv.Itoa(gc.Response.KeyId))
+	md.Append("key_type", gc.Response.KeyType)
+	md.Append("user_id", gc.Response.UserId)
+	md.Append("username", gc.Response.Username)
 	md.Append("remote_ip", env.RemoteAddr)
 	ctx = metadata.NewOutgoingContext(ctx, md)
 
 	return ctx, cancel
 }
 
-func (gc *GitalyCommand) LogExecution(ctx context.Context, repository *pb.Repository, response *accessverifier.Response, env sshenv.Env) {
+func (gc *GitalyCommand) LogExecution(ctx context.Context, repository *pb.Repository, env sshenv.Env) {
 	fields := log.Fields{
-		"command":         gc.ServiceName,
+		"command":         gc.Command.ServiceName,
 		"gl_project_path": repository.GlProjectPath,
 		"gl_repository":   repository.GlRepository,
-		"user_id":         response.UserId,
-		"username":        response.Username,
+		"user_id":         gc.Response.UserId,
+		"username":        gc.Response.Username,
 		"git_protocol":    env.GitProtocolVersion,
 		"remote_ip":       env.RemoteAddr,
-		"gl_key_type":     response.KeyType,
-		"gl_key_id":       response.KeyId,
+		"gl_key_type":     gc.Response.KeyType,
+		"gl_key_id":       gc.Response.KeyId,
 	}
 
 	log.WithContextFields(ctx, fields).Info("executing git command")
@@ -118,53 +119,6 @@
 	return metadata.NewOutgoingContext(ctx, md)
 }
 
-func getConn(ctx context.Context, gc *GitalyCommand, registry *client.SidechannelRegistry) (*grpc.ClientConn, error) {
-	if gc.Address == "" {
-		return nil, fmt.Errorf("no gitaly_address given")
-	}
-
-	serviceName := correlation.ExtractClientNameFromContext(ctx)
-	if serviceName == "" {
-		serviceName = "gitlab-shell-unknown"
-
-		log.WithContextFields(ctx, log.Fields{"service_name": serviceName}).Warn("No gRPC service name specified, defaulting to gitlab-shell-unknown")
-	}
-
-	serviceName = fmt.Sprintf("%s-%s", serviceName, gc.ServiceName)
-
-	connOpts := client.DefaultDialOpts
-	connOpts = append(
-		connOpts,
-		grpc.WithStreamInterceptor(
-			grpc_middleware.ChainStreamClient(
-				grpctracing.StreamClientTracingInterceptor(),
-				grpc_prometheus.StreamClientInterceptor,
-				grpccorrelation.StreamClientCorrelationInterceptor(
-					grpccorrelation.WithClientName(serviceName),
-				),
-			),
-		),
-
-		grpc.WithUnaryInterceptor(
-			grpc_middleware.ChainUnaryClient(
-				grpctracing.UnaryClientTracingInterceptor(),
-				grpc_prometheus.UnaryClientInterceptor,
-				grpccorrelation.UnaryClientCorrelationInterceptor(
-					grpccorrelation.WithClientName(serviceName),
-				),
-			),
-		),
-	)
-
-	if gc.Token != "" {
-		connOpts = append(connOpts,
-			grpc.WithPerRPCCredentials(gitalyauth.RPCCredentialsV2(gc.Token)),
-		)
-	}
-
-	if gc.DialSidechannel {
-		return client.DialSidechannel(ctx, gc.Address, registry, connOpts)
-	}
-
-	return client.DialContext(ctx, gc.Address, connOpts)
+func (gc *GitalyCommand) getConn(ctx context.Context) (*grpc.ClientConn, error) {
+	return gc.Config.GitalyClient.GetConnection(ctx, gc.Command)
 }
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -11,15 +11,15 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
-func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn, *client.SidechannelRegistry) (int32, error) {
-	return func(ctx context.Context, client *grpc.ClientConn, registry *client.SidechannelRegistry) (int32, error) {
+func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn) (int32, error) {
+	return func(ctx context.Context, client *grpc.ClientConn) (int32, error) {
 		require.NotNil(t, ctx)
 		require.NotNil(t, client)
 
@@ -28,10 +28,13 @@
 }
 
 func TestRunGitalyCommand(t *testing.T) {
-	cmd := GitalyCommand{
-		Config:  &config.Config{},
-		Address: "tcp://localhost:9999",
-	}
+	cmd := NewGitalyCommand(
+		&config.Config{},
+		string(commandargs.UploadPack),
+		&accessverifier.Response{
+			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
+		},
+	)
 
 	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, nil))
 	require.NoError(t, err)
@@ -41,6 +44,32 @@
 	require.Equal(t, err, expectedErr)
 }
 
+func TestCachingOfGitalyConnections(t *testing.T) {
+	ctx := context.Background()
+	cfg := &config.Config{}
+	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+	response := &accessverifier.Response{
+		Username: "user",
+		Gitaly: accessverifier.Gitaly{
+			Address:        "tcp://localhost:9999",
+			Token:          "token",
+			UseSidechannel: true,
+		},
+	}
+
+	cmd := NewGitalyCommand(cfg, string(commandargs.UploadPack), response)
+
+	conn, err := cmd.getConn(ctx)
+	require.NoError(t, err)
+
+	// Reuses connection for different users
+	response.Username = "another-user"
+	cmd = NewGitalyCommand(cfg, string(commandargs.UploadPack), response)
+	newConn, err := cmd.getConn(ctx)
+	require.NoError(t, err)
+	require.Equal(t, conn, newConn)
+}
+
 func TestMissingGitalyAddress(t *testing.T) {
 	cmd := GitalyCommand{Config: &config.Config{}}
 
@@ -49,10 +78,13 @@
 }
 
 func TestUnavailableGitalyErr(t *testing.T) {
-	cmd := GitalyCommand{
-		Config:  &config.Config{},
-		Address: "tcp://localhost:9999",
-	}
+	cmd := NewGitalyCommand(
+		&config.Config{},
+		string(commandargs.UploadPack),
+		&accessverifier.Response{
+			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
+		},
+	)
 
 	expectedErr := grpcstatus.Error(grpccodes.Unavailable, "error")
 	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, expectedErr))
@@ -68,15 +100,20 @@
 	}{
 		{
 			name: "gitaly_feature_flags",
-			gc: &GitalyCommand{
-				Config:  &config.Config{},
-				Address: "tcp://localhost:9999",
-				Features: map[string]string{
-					"gitaly-feature-cache_invalidator":        "true",
-					"other-ff":                                "true",
-					"gitaly-feature-inforef_uploadpack_cache": "false",
+			gc: NewGitalyCommand(
+				&config.Config{},
+				string(commandargs.UploadPack),
+				&accessverifier.Response{
+					Gitaly: accessverifier.Gitaly{
+						Address: "tcp://localhost:9999",
+						Features: map[string]string{
+							"gitaly-feature-cache_invalidator":        "true",
+							"other-ff":                                "true",
+							"gitaly-feature-inforef_uploadpack_cache": "false",
+						},
+					},
 				},
-			},
+			),
 			want: map[string]string{
 				"gitaly-feature-cache_invalidator":        "true",
 				"gitaly-feature-inforef_uploadpack_cache": "false",
@@ -87,7 +124,7 @@
 		t.Run(tt.name, func(t *testing.T) {
 			cmd := tt.gc
 
-			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn, _ *client.SidechannelRegistry) (int32, error) {
+			err := cmd.RunGitalyCommand(context.Background(), func(ctx context.Context, _ *grpc.ClientConn) (int32, error) {
 				md, exists := metadata.FromOutgoingContext(ctx)
 				require.True(t, exists)
 				require.Equal(t, len(tt.want), md.Len())
@@ -117,10 +154,19 @@
 	}{
 		{
 			name: "client_identity",
-			gc: &GitalyCommand{
-				Config:  &config.Config{},
-				Address: "tcp://localhost:9999",
-			},
+			gc: NewGitalyCommand(
+				&config.Config{},
+				string(commandargs.UploadPack),
+				&accessverifier.Response{
+					KeyId:    1,
+					KeyType:  "key",
+					UserId:   "6",
+					Username: "jane.doe",
+					Gitaly: accessverifier.Gitaly{
+						Address: "tcp://localhost:9999",
+					},
+				},
+			),
 			env: sshenv.Env{
 				GitProtocolVersion: "protocol",
 				IsSSHConnection:    true,
@@ -134,12 +180,6 @@
 				GlRepository:                  "project-26",
 				GlProjectPath:                 "group/private",
 			},
-			response: &accessverifier.Response{
-				KeyId:    1,
-				KeyType:  "key",
-				UserId:   "6",
-				Username: "jane.doe",
-			},
 			want: map[string]string{
 				"key_id":    "1",
 				"key_type":  "key",
@@ -153,7 +193,7 @@
 		t.Run(tt.name, func(t *testing.T) {
 			ctx := context.Background()
 
-			ctx, cancel := tt.gc.PrepareContext(ctx, tt.repo, tt.response, tt.env)
+			ctx, cancel := tt.gc.PrepareContext(ctx, tt.repo, tt.env)
 			defer cancel()
 
 			md, exists := metadata.FromOutgoingContext(ctx)
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -22,6 +22,14 @@
 	return inFmt
 }
 
+func logLevel(inLevel string) string {
+	if inLevel == "" {
+		return "info"
+	}
+
+	return inLevel
+}
+
 func logFile(inFile string) string {
 	if inFile == "" {
 		return "stderr"
@@ -35,7 +43,7 @@
 		log.WithFormatter(logFmt(cfg.LogFormat)),
 		log.WithOutputName(logFile(cfg.LogFile)),
 		log.WithTimezone(time.UTC),
-		log.WithLogLevel(cfg.LogLevel),
+		log.WithLogLevel(logLevel(cfg.LogLevel)),
 	}
 }
 
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -30,9 +30,11 @@
 	tmpFile.Close()
 
 	data, err := os.ReadFile(tmpFile.Name())
+	dataStr := string(data)
 	require.NoError(t, err)
-	require.Contains(t, string(data), `msg":"this is a test"`)
-	require.NotContains(t, string(data), `msg:":"debug log message"`)
+	require.Contains(t, dataStr, `"msg":"this is a test"`)
+	require.NotContains(t, dataStr, `"msg":"debug log message"`)
+	require.NotContains(t, dataStr, `"msg":"unknown log level`)
 }
 
 func TestConfigureWithDebugLogLevel(t *testing.T) {
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -9,36 +9,51 @@
 )
 
 const (
-	namespace     = "gitlab_shell"
-	sshdSubsystem = "sshd"
-	httpSubsystem = "http"
+	namespace       = "gitlab_shell"
+	sshdSubsystem   = "sshd"
+	httpSubsystem   = "http"
+	gitalySubsystem = "gitaly"
 
 	httpInFlightRequestsMetricName       = "in_flight_requests"
 	httpRequestsTotalMetricName          = "requests_total"
 	httpRequestDurationSecondsMetricName = "request_duration_seconds"
 
-	sshdConnectionsInFlightName = "in_flight_connections"
-	sshdConnectionDuration      = "connection_duration_seconds"
-	sshdHitMaxSessions          = "concurrent_limited_sessions_total"
+	sshdConnectionsInFlightName               = "in_flight_connections"
+	sshdHitMaxSessionsName                    = "concurrent_limited_sessions_total"
+	sshdSessionDurationSecondsName            = "session_duration_seconds"
+	sshdSessionEstablishedDurationSecondsName = "session_established_duration_seconds"
+
+	sliSshdSessionsTotalName       = "gitlab_sli:shell_sshd_sessions:total"
+	sliSshdSessionsErrorsTotalName = "gitlab_sli:shell_sshd_sessions:errors_total"
+
+	gitalyConnectionsTotalName = "connections_total"
 )
 
 var (
-	SshdConnectionDuration = promauto.NewHistogram(
+	SshdSessionDuration = promauto.NewHistogram(
 		prometheus.HistogramOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      sshdConnectionDuration,
+			Name:      sshdSessionDurationSecondsName,
 			Help:      "A histogram of latencies for connections to gitlab-shell sshd.",
 			Buckets: []float64{
-				0.005, /* 5ms */
-				0.025, /* 25ms */
-				0.1,   /* 100ms */
-				0.5,   /* 500ms */
-				1.0,   /* 1s */
-				10.0,  /* 10s */
-				30.0,  /* 30s */
-				60.0,  /* 1m */
-				300.0, /* 5m */
+				5.0,  /* 5s */
+				30.0, /* 30s */
+				60.0, /* 1m */
+			},
+		},
+	)
+
+	SshdSessionEstablishedDuration = promauto.NewHistogram(
+		prometheus.HistogramOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      sshdSessionEstablishedDurationSecondsName,
+			Help:      "A histogram of latencies until session established to gitlab-shell sshd.",
+			Buckets: []float64{
+				0.5, /* 5ms */
+				1.0, /* 1s */
+				5.0, /* 5s */
 			},
 		},
 	)
@@ -56,11 +71,35 @@
 		prometheus.CounterOpts{
 			Namespace: namespace,
 			Subsystem: sshdSubsystem,
-			Name:      sshdHitMaxSessions,
+			Name:      sshdHitMaxSessionsName,
 			Help:      "The number of times the concurrent sessions limit was hit in gitlab-shell sshd.",
 		},
 	)
 
+	SliSshdSessionsTotal = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Name: sliSshdSessionsTotalName,
+			Help: "Number of SSH sessions that have been established",
+		},
+	)
+
+	SliSshdSessionsErrorsTotal = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Name: sliSshdSessionsErrorsTotalName,
+			Help: "Number of SSH sessions that have failed",
+		},
+	)
+
+	GitalyConnectionsTotal = promauto.NewCounterVec(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: gitalySubsystem,
+			Name:      gitalyConnectionsTotalName,
+			Help:      "Number of Gitaly connections that have been established",
+		},
+		[]string{"status"},
+	)
+
 	// The metrics and the buckets size are similar to the ones we have for handlers in Labkit
 	// When the MR: https://gitlab.com/gitlab-org/labkit/-/merge_requests/150 is merged,
 	// these metrics can be refactored out of Gitlab Shell code by using the helper function from Labkit
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -2,7 +2,6 @@
 
 import (
 	"context"
-	"time"
 
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
@@ -13,7 +12,6 @@
 )
 
 type connection struct {
-	begin              time.Time
 	concurrentSessions *semaphore.Weighted
 	remoteAddr         string
 }
@@ -22,7 +20,6 @@
 
 func newConnection(maxSessions int64, remoteAddr string) *connection {
 	return &connection{
-		begin:              time.Now(),
 		concurrentSessions: semaphore.NewWeighted(maxSessions),
 		remoteAddr:         remoteAddr,
 	}
@@ -31,11 +28,6 @@
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
-	metrics.SshdConnectionsInFlight.Inc()
-	defer metrics.SshdConnectionsInFlight.Dec()
-
-	defer metrics.SshdConnectionDuration.Observe(time.Since(c.begin).Seconds())
-
 	for newChannel := range chans {
 		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -22,6 +22,7 @@
 	channel     ssh.Channel
 	gitlabKeyId string
 	remoteAddr  string
+	success     bool
 
 	// State managed by the session
 	execCmd            string
@@ -182,6 +183,8 @@
 	log.WithContextFields(ctx, log.Fields{"exit_status": status}).Info("session: exit: exiting")
 	req := exitStatusReq{ExitStatus: status}
 
+	s.success = status == 0
+
 	s.channel.CloseWrite()
 	s.channel.SendRequest("exit-status", false, ssh.Marshal(req))
 }
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -99,6 +99,7 @@
 		expectedExecCmd    string
 		sentRequestName    string
 		sentRequestPayload []byte
+		success            bool
 	}{
 		{
 			desc:            "invalid payload",
@@ -111,6 +112,7 @@
 			expectedExecCmd:    "discover",
 			sentRequestName:    "exit-status",
 			sentRequestPayload: ssh.Marshal(exitStatusReq{ExitStatus: 0}),
+			success:            true,
 		},
 	}
 
@@ -130,6 +132,7 @@
 			require.Equal(t, false, s.handleExec(context.Background(), r))
 			require.Equal(t, tc.sentRequestName, f.sentRequestName)
 			require.Equal(t, tc.sentRequestPayload, f.sentRequestPayload)
+			require.Equal(t, tc.success, s.success)
 		})
 	}
 }
@@ -141,6 +144,7 @@
 		errMsg           string
 		gitlabKeyId      string
 		expectedExitCode uint32
+		success          bool
 	}{
 		{
 			desc:             "fails to parse command",
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -12,6 +12,7 @@
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/log"
@@ -145,6 +146,20 @@
 }
 
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
+	success := false
+
+	metrics.SshdConnectionsInFlight.Inc()
+	started := time.Now()
+	defer func() {
+		metrics.SshdConnectionsInFlight.Dec()
+		metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
+
+		metrics.SliSshdSessionsTotal.Inc()
+		if !success {
+			metrics.SliSshdSessionsErrorsTotal.Inc()
+		}
+	}()
+
 	remoteAddr := nconn.RemoteAddr().String()
 
 	defer s.wg.Done()
@@ -164,7 +179,7 @@
 
 	ctxlog.Info("server: handleConn: start")
 
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
+	sconn, chans, reqs, err := s.initSSHConnection(ctx, nconn)
 	if err != nil {
 		ctxlog.WithError(err).Error("server: handleConn: failed to initialize SSH connection")
 		return
@@ -172,8 +187,12 @@
 
 	go ssh.DiscardRequests(reqs)
 
+	var establishSessionDuration float64
 	conn := newConnection(s.Config.Server.ConcurrentSessionsLimit, remoteAddr)
 	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
+		establishSessionDuration = time.Since(started).Seconds()
+		metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
+
 		session := &session{
 			cfg:         s.Config,
 			channel:     channel,
@@ -182,9 +201,28 @@
 		}
 
 		session.handle(ctx, requests)
+
+		success = session.success
 	})
 
-	ctxlog.Info("server: handleConn: done")
+	ctxlog.WithFields(log.Fields{
+		"duration_s":                   time.Since(started).Seconds(),
+		"establish_session_duration_s": establishSessionDuration,
+	}).Info("server: handleConn: done")
+}
+
+func (s *Server) initSSHConnection(ctx context.Context, nconn net.Conn) (sconn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request, err error) {
+	timer := time.AfterFunc(s.Config.Server.LoginGraceTime(), func() {
+		nconn.Close()
+	})
+	defer func() {
+		// If time.Stop() equals false, that means that AfterFunc has been executed
+		if !timer.Stop() {
+			err = fmt.Errorf("initSSHConnection: ssh handshake timeout of %v", s.Config.Server.LoginGraceTime())
+		}
+	}()
+
+	return ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 }
 
 func unconditionalRequirePolicy(_ net.Addr) (proxyproto.Policy, error) {
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -3,6 +3,7 @@
 import (
 	"context"
 	"fmt"
+	"net"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -134,6 +135,33 @@
 	require.Nil(t, s.Shutdown())
 }
 
+func TestLoginGraceTime(t *testing.T) {
+	s := setupServer(t)
+
+	unauthenticatedRequestStatus := make(chan string)
+	completed := make(chan bool)
+
+	clientCfg := clientConfig(t)
+	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
+		unauthenticatedRequestStatus <- "authentication-started"
+		<-completed // Wait infinitely
+
+		return nil
+	}
+
+	go func() {
+		// Start an SSH connection that never ends
+		ssh.Dial("tcp", serverUrl, clientCfg)
+	}()
+
+	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
+
+	// Verify that the server shutdowns instantly without waiting for any connection to execute
+	// That means that the server doesn't have any connections to wait for
+	require.NoError(t, s.Shutdown())
+	verifyStatus(t, s, StatusClosed)
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
@@ -183,6 +211,7 @@
 	cfg.User = user
 	cfg.Server.Listen = serverUrl
 	cfg.Server.ConcurrentSessionsLimit = 1
+	cfg.Server.LoginGraceTimeSeconds = 1
 	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
 
 	s, err := NewServer(cfg)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652658086 -14400
#      Mon May 16 03:41:26 2022 +0400
# Node ID b09a5bfed30e6b24b54b4233aa8b34a409288b52
# Parent  95f105751282580f695ff4bec8577de261720bbd
Remove deprecated bundler-audit

It's been removed in:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86704

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -92,9 +92,6 @@
 gemnasium-dependency_scanning:
   rules: *workflow_rules
 
-bundler-audit-dependency_scanning:
-  rules: *workflow_rules
-
 # Secret Detection
 secret_detection:
   rules: *workflow_rules
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652687897 0
#      Mon May 16 07:58:17 2022 +0000
# Node ID 69a1d91856762023270fdc879e015f95444acb2a
# Parent  95f105751282580f695ff4bec8577de261720bbd
# Parent  b09a5bfed30e6b24b54b4233aa8b34a409288b52
Merge branch 'id-fix-ci-pipeline' into 'main'

Remove deprecated bundler-audit

See merge request gitlab-org/gitlab-shell!626

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -92,9 +92,6 @@
 gemnasium-dependency_scanning:
   rules: *workflow_rules
 
-bundler-audit-dependency_scanning:
-  rules: *workflow_rules
-
 # Secret Detection
 secret_detection:
   rules: *workflow_rules
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652649472 -14400
#      Mon May 16 01:17:52 2022 +0400
# Node ID abc89bd8192dce763944a2f6dd61371c08d75db6
# Parent  69a1d91856762023270fdc879e015f95444acb2a
Return error from session handler

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -22,7 +22,7 @@
 	sconn              *ssh.ServerConn
 }
 
-type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request)
+type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request) error
 
 func newConnection(cfg *config.Config, remoteAddr string, sconn *ssh.ServerConn) *connection {
 	return &connection{
@@ -76,7 +76,12 @@
 				}
 			}()
 
-			handler(ctx, channel, requests)
+			metrics.SliSshdSessionsTotal.Inc()
+			err := handler(ctx, channel, requests)
+			if err != nil {
+				metrics.SliSshdSessionsErrorsTotal.Inc()
+			}
+
 			ctxlog.Info("connection: handle: done")
 		}()
 	}
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -55,9 +55,14 @@
 	ssh.Conn
 
 	sentRequestName string
+	waitErr         error
 	mu              sync.Mutex
 }
 
+func (f *fakeConn) Wait() error {
+	return f.waitErr
+}
+
 func (f *fakeConn) SentRequestName() string {
 	f.mu.Lock()
 	defer f.mu.Unlock()
@@ -90,7 +95,7 @@
 
 	numSessions := 0
 	require.NotPanics(t, func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 			numSessions += 1
 			close(chans)
 			panic("This is a panic")
@@ -128,8 +133,9 @@
 	defer cancel()
 
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 			<-ctx.Done() // Keep the accepted channel open until the end of the test
+			return nil
 		})
 	}()
 
@@ -142,9 +148,10 @@
 	conn, chans := setup(1, newChannel)
 
 	channelHandled := false
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 		channelHandled = true
 		close(chans)
+		return nil
 	})
 
 	require.True(t, channelHandled)
@@ -160,8 +167,9 @@
 
 	channelHandled := false
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 			channelHandled = true
+			return nil
 		})
 	}()
 
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -22,7 +22,6 @@
 	channel     ssh.Channel
 	gitlabKeyId string
 	remoteAddr  string
-	success     bool
 
 	// State managed by the session
 	execCmd            string
@@ -42,11 +41,12 @@
 	ExitStatus uint32
 }
 
-func (s *session) handle(ctx context.Context, requests <-chan *ssh.Request) {
+func (s *session) handle(ctx context.Context, requests <-chan *ssh.Request) error {
 	ctxlog := log.ContextLogger(ctx)
 
 	ctxlog.Debug("session: handle: entering request loop")
 
+	var err error
 	for req := range requests {
 		sessionLog := ctxlog.WithFields(log.Fields{
 			"bytesize":   len(req.Payload),
@@ -58,12 +58,14 @@
 		var shouldContinue bool
 		switch req.Type {
 		case "env":
-			shouldContinue = s.handleEnv(ctx, req)
+			shouldContinue, err = s.handleEnv(ctx, req)
 		case "exec":
-			shouldContinue = s.handleExec(ctx, req)
+			shouldContinue, err = s.handleExec(ctx, req)
 		case "shell":
 			shouldContinue = false
-			s.exit(ctx, s.handleShell(ctx, req))
+			var status uint32
+			status, err = s.handleShell(ctx, req)
+			s.exit(ctx, status)
 		default:
 			// Ignore unknown requests but don't terminate the session
 			shouldContinue = true
@@ -84,15 +86,17 @@
 	}
 
 	ctxlog.Debug("session: handle: exiting request loop")
+
+	return err
 }
 
-func (s *session) handleEnv(ctx context.Context, req *ssh.Request) bool {
+func (s *session) handleEnv(ctx context.Context, req *ssh.Request) (bool, error) {
 	var accepted bool
 	var envRequest envRequest
 
 	if err := ssh.Unmarshal(req.Payload, &envRequest); err != nil {
 		log.ContextLogger(ctx).WithError(err).Error("session: handleEnv: failed to unmarshal request")
-		return false
+		return false, err
 	}
 
 	switch envRequest.Name {
@@ -113,23 +117,24 @@
 		ctx, log.Fields{"accepted": accepted, "env_request": envRequest},
 	).Debug("session: handleEnv: processed")
 
-	return true
+	return true, nil
 }
 
-func (s *session) handleExec(ctx context.Context, req *ssh.Request) bool {
+func (s *session) handleExec(ctx context.Context, req *ssh.Request) (bool, error) {
 	var execRequest execRequest
 	if err := ssh.Unmarshal(req.Payload, &execRequest); err != nil {
-		return false
+		return false, err
 	}
 
 	s.execCmd = execRequest.Command
 
-	s.exit(ctx, s.handleShell(ctx, req))
+	status, err := s.handleShell(ctx, req)
+	s.exit(ctx, status)
 
-	return false
+	return false, err
 }
 
-func (s *session) handleShell(ctx context.Context, req *ssh.Request) uint32 {
+func (s *session) handleShell(ctx context.Context, req *ssh.Request) (uint32, error) {
 	ctxlog := log.ContextLogger(ctx)
 
 	if req.WantReply {
@@ -157,7 +162,7 @@
 			s.toStderr(ctx, "Failed to parse command: %v\n", err.Error())
 		}
 		s.toStderr(ctx, "Unknown command: %v\n", s.execCmd)
-		return 128
+		return 128, err
 	}
 
 	cmdName := reflect.TypeOf(cmd).String()
@@ -165,12 +170,12 @@
 
 	if err := cmd.Execute(ctx); err != nil {
 		s.toStderr(ctx, "remote: ERROR: %v\n", err.Error())
-		return 1
+		return 1, err
 	}
 
 	ctxlog.Info("session: handleShell: command executed successfully")
 
-	return 0
+	return 0, nil
 }
 
 func (s *session) toStderr(ctx context.Context, format string, args ...interface{}) {
@@ -183,8 +188,6 @@
 	log.WithContextFields(ctx, log.Fields{"exit_status": status}).Info("session: exit: exiting")
 	req := exitStatusReq{ExitStatus: status}
 
-	s.success = status == 0
-
 	s.channel.CloseWrite()
 	s.channel.SendRequest("exit-status", false, ssh.Marshal(req))
 }
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -3,6 +3,7 @@
 import (
 	"bytes"
 	"context"
+	"errors"
 	"io"
 	"net/http"
 	"testing"
@@ -60,22 +61,26 @@
 	testCases := []struct {
 		desc                    string
 		payload                 []byte
+		expectedErr             error
 		expectedProtocolVersion string
 		expectedResult          bool
 	}{
 		{
 			desc:                    "invalid payload",
 			payload:                 []byte("invalid"),
+			expectedErr:             errors.New("ssh: unmarshal error for field Name of type envRequest"),
 			expectedProtocolVersion: "1",
 			expectedResult:          false,
 		}, {
 			desc:                    "valid payload",
 			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL", Value: "2"}),
+			expectedErr:             nil,
 			expectedProtocolVersion: "2",
 			expectedResult:          true,
 		}, {
 			desc:                    "valid payload with forbidden env var",
 			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL_ENV", Value: "2"}),
+			expectedErr:             nil,
 			expectedProtocolVersion: "1",
 			expectedResult:          true,
 		},
@@ -86,8 +91,11 @@
 			s := &session{gitProtocolVersion: "1"}
 			r := &ssh.Request{Payload: tc.payload}
 
-			require.Equal(t, s.handleEnv(context.Background(), r), tc.expectedResult)
-			require.Equal(t, s.gitProtocolVersion, tc.expectedProtocolVersion)
+			shouldContinue, err := s.handleEnv(context.Background(), r)
+
+			require.Equal(t, tc.expectedErr, err)
+			require.Equal(t, tc.expectedResult, shouldContinue)
+			require.Equal(t, tc.expectedProtocolVersion, s.gitProtocolVersion)
 		})
 	}
 }
@@ -96,23 +104,24 @@
 	testCases := []struct {
 		desc               string
 		payload            []byte
+		expectedErr        error
 		expectedExecCmd    string
 		sentRequestName    string
 		sentRequestPayload []byte
-		success            bool
 	}{
 		{
 			desc:            "invalid payload",
 			payload:         []byte("invalid"),
+			expectedErr:     errors.New("ssh: unmarshal error for field Command of type execRequest"),
 			expectedExecCmd: "",
 			sentRequestName: "",
 		}, {
 			desc:               "valid payload",
 			payload:            ssh.Marshal(execRequest{Command: "discover"}),
+			expectedErr:        nil,
 			expectedExecCmd:    "discover",
 			sentRequestName:    "exit-status",
 			sentRequestPayload: ssh.Marshal(exitStatusReq{ExitStatus: 0}),
-			success:            true,
 		},
 	}
 
@@ -129,47 +138,53 @@
 			}
 			r := &ssh.Request{Payload: tc.payload}
 
-			require.Equal(t, false, s.handleExec(context.Background(), r))
+			shouldContinue, err := s.handleExec(context.Background(), r)
+
+			require.Equal(t, tc.expectedErr, err)
+			require.Equal(t, false, shouldContinue)
 			require.Equal(t, tc.sentRequestName, f.sentRequestName)
 			require.Equal(t, tc.sentRequestPayload, f.sentRequestPayload)
-			require.Equal(t, tc.success, s.success)
 		})
 	}
 }
 
 func TestHandleShell(t *testing.T) {
 	testCases := []struct {
-		desc             string
-		cmd              string
-		errMsg           string
-		gitlabKeyId      string
-		expectedExitCode uint32
-		success          bool
+		desc              string
+		cmd               string
+		errMsg            string
+		gitlabKeyId       string
+		expectedErrString string
+		expectedExitCode  uint32
 	}{
 		{
-			desc:             "fails to parse command",
-			cmd:              `\`,
-			errMsg:           "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
-			gitlabKeyId:      "root",
-			expectedExitCode: 128,
+			desc:              "fails to parse command",
+			cmd:               `\`,
+			errMsg:            "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
+			gitlabKeyId:       "root",
+			expectedErrString: "Invalid SSH command: invalid command line string",
+			expectedExitCode:  128,
 		}, {
-			desc:             "specified command is unknown",
-			cmd:              "unknown-command",
-			errMsg:           "Unknown command: unknown-command\n",
-			gitlabKeyId:      "root",
-			expectedExitCode: 128,
+			desc:              "specified command is unknown",
+			cmd:               "unknown-command",
+			errMsg:            "Unknown command: unknown-command\n",
+			gitlabKeyId:       "root",
+			expectedErrString: "Disallowed command",
+			expectedExitCode:  128,
 		}, {
-			desc:             "fails to parse command",
-			cmd:              "discover",
-			gitlabKeyId:      "",
-			errMsg:           "remote: ERROR: Failed to get username: who='' is invalid\n",
-			expectedExitCode: 1,
+			desc:              "fails to parse command",
+			cmd:               "discover",
+			gitlabKeyId:       "",
+			errMsg:            "remote: ERROR: Failed to get username: who='' is invalid\n",
+			expectedErrString: "Failed to get username: who='' is invalid",
+			expectedExitCode:  1,
 		}, {
-			desc:             "fails to parse command",
-			cmd:              "discover",
-			errMsg:           "",
-			gitlabKeyId:      "root",
-			expectedExitCode: 0,
+			desc:              "fails to parse command",
+			cmd:               "discover",
+			errMsg:            "",
+			gitlabKeyId:       "root",
+			expectedErrString: "",
+			expectedExitCode:  0,
 		},
 	}
 
@@ -186,7 +201,13 @@
 			}
 			r := &ssh.Request{}
 
-			require.Equal(t, tc.expectedExitCode, s.handleShell(context.Background(), r))
+			exitCode, err := s.handleShell(context.Background(), r)
+
+			if tc.expectedErrString != "" {
+				require.Equal(t, tc.expectedErrString, err.Error())
+			}
+
+			require.Equal(t, tc.expectedExitCode, exitCode)
 			require.Equal(t, tc.errMsg, out.String())
 		})
 	}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -181,7 +181,7 @@
 	started := time.Now()
 	var establishSessionDuration float64
 	conn := newConnection(s.Config, remoteAddr, sconn)
-	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
+	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) error {
 		establishSessionDuration = time.Since(started).Seconds()
 		metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
 
@@ -192,11 +192,7 @@
 			remoteAddr:  remoteAddr,
 		}
 
-		metrics.SliSshdSessionsTotal.Inc()
-		session.handle(ctx, requests)
-		if !session.success {
-			metrics.SliSshdSessionsErrorsTotal.Inc()
-		}
+		return session.handle(ctx, requests)
 	})
 
 	reason := sconn.Wait()
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652650327 -14400
#      Mon May 16 01:32:07 2022 +0400
# Node ID af39d7ea02da4bdd77c83b08b60e3efbeae2f810
# Parent  abc89bd8192dce763944a2f6dd61371c08d75db6
Wait until all Gitaly sessions are executed

If they haven't been executed within a timeout, we unblock the
execution.

Issue: https://gitlab.com/gitlab-org/gitlab-shell/-/issues/563

We have an issue when a lot of git clones operations get canceled.
It was assumed that some clients close the connection just after
all the data has been received from Git server. If there was a
network delay and gitlab-sshd hadn't managed to gracefully close
the connection, context get canceled and Gitaly cancels the
execution and returns the error.

Let's wait for a perion to allow Gitaly to gracefully complete the
operation

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -15,19 +15,25 @@
 
 const KeepAliveMsg = "keepalive@openssh.com"
 
+var EOFTimeout = 10 * time.Second
+
 type connection struct {
 	cfg                *config.Config
 	concurrentSessions *semaphore.Weighted
 	remoteAddr         string
 	sconn              *ssh.ServerConn
+	maxSessions        int64
 }
 
 type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request) error
 
 func newConnection(cfg *config.Config, remoteAddr string, sconn *ssh.ServerConn) *connection {
+	maxSessions := cfg.Server.ConcurrentSessionsLimit
+
 	return &connection{
 		cfg:                cfg,
-		concurrentSessions: semaphore.NewWeighted(cfg.Server.ConcurrentSessionsLimit),
+		maxSessions:        maxSessions,
+		concurrentSessions: semaphore.NewWeighted(maxSessions),
 		remoteAddr:         remoteAddr,
 		sconn:              sconn,
 	}
@@ -85,6 +91,14 @@
 			ctxlog.Info("connection: handle: done")
 		}()
 	}
+
+	// When a connection has been prematurely closed we block execution until all concurrent sessions are released
+	// in order to allow Gitaly complete the operations and close all the channels gracefully.
+	// If it didn't happen within timeout, we unblock the execution
+	// Related issue: https://gitlab.com/gitlab-org/gitlab-shell/-/issues/563
+	ctx, cancel := context.WithTimeout(ctx, EOFTimeout)
+	defer cancel()
+	c.concurrentSessions.Acquire(ctx, c.maxSessions)
 }
 
 func (c *connection) sendKeepAliveMsg(ctx context.Context, ticker *time.Ticker) {
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -55,14 +55,9 @@
 	ssh.Conn
 
 	sentRequestName string
-	waitErr         error
 	mu              sync.Mutex
 }
 
-func (f *fakeConn) Wait() error {
-	return f.waitErr
-}
-
 func (f *fakeConn) SentRequestName() string {
 	f.mu.Lock()
 	defer f.mu.Unlock()
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652650600 -14400
#      Mon May 16 01:36:40 2022 +0400
# Node ID 7a17aa4c8548a037a05e4f60d2d0095bc1a46485
# Parent  af39d7ea02da4bdd77c83b08b60e3efbeae2f810
Log canceled requests into separate metrics

When a request get canceled we don't want to consider it an error

diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -39,7 +39,7 @@
 	require.NoError(t, err)
 
 	var actualNames []string
-	for _, m := range ms[0:9] {
+	for _, m := range ms[0:10] {
 		actualNames = append(actualNames, m.GetName())
 	}
 
@@ -47,6 +47,7 @@
 		"gitlab_shell_http_in_flight_requests",
 		"gitlab_shell_http_request_duration_seconds",
 		"gitlab_shell_http_requests_total",
+		"gitlab_shell_sshd_canceled_sessions",
 		"gitlab_shell_sshd_concurrent_limited_sessions_total",
 		"gitlab_shell_sshd_in_flight_connections",
 		"gitlab_shell_sshd_session_duration_seconds",
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -22,6 +22,7 @@
 	sshdHitMaxSessionsName                    = "concurrent_limited_sessions_total"
 	sshdSessionDurationSecondsName            = "session_duration_seconds"
 	sshdSessionEstablishedDurationSecondsName = "session_established_duration_seconds"
+	sshdCanceledSessionsName                  = "canceled_sessions"
 
 	sliSshdSessionsTotalName       = "gitlab_sli:shell_sshd_sessions:total"
 	sliSshdSessionsErrorsTotalName = "gitlab_sli:shell_sshd_sessions:errors_total"
@@ -76,6 +77,15 @@
 		},
 	)
 
+	SshdCanceledSessions = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      sshdCanceledSessionsName,
+			Help:      "The number of canceled gitlab-sshd sessions.",
+		},
+	)
+
 	SliSshdSessionsTotal = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Name: sliSshdSessionsTotalName,
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -6,6 +6,8 @@
 
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
@@ -85,7 +87,11 @@
 			metrics.SliSshdSessionsTotal.Inc()
 			err := handler(ctx, channel, requests)
 			if err != nil {
-				metrics.SliSshdSessionsErrorsTotal.Inc()
+				if grpcstatus.Convert(err).Code() == grpccodes.Canceled {
+					metrics.SshdCanceledSessions.Inc()
+				} else {
+					metrics.SliSshdSessionsErrorsTotal.Inc()
+				}
 			}
 
 			ctxlog.Info("connection: handle: done")
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -7,10 +7,14 @@
 	"testing"
 	"time"
 
+	"github.com/prometheus/client_golang/prometheus/testutil"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
 
 type rejectCall struct {
@@ -189,3 +193,33 @@
 
 	require.Eventually(t, func() bool { return KeepAliveMsg == f.SentRequestName() }, time.Second, time.Millisecond)
 }
+
+func TestSessionsMetrics(t *testing.T) {
+	// Unfortunately, there is no working way to reset Counter (not CounterVec)
+	// https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#pkg-index
+	initialSessionsTotal := testutil.ToFloat64(metrics.SliSshdSessionsTotal)
+	initialSessionsErrorTotal := testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal)
+	initialCanceledSessions := testutil.ToFloat64(metrics.SshdCanceledSessions)
+
+	newChannel := &fakeNewChannel{channelType: "session"}
+
+	conn, chans := setup(1, newChannel)
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		close(chans)
+		return errors.New("custom error")
+	})
+
+	require.InDelta(t, initialSessionsTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+	require.InDelta(t, initialCanceledSessions, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
+
+	conn, chans = setup(1, newChannel)
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		close(chans)
+		return grpcstatus.Error(grpccodes.Canceled, "error")
+	})
+
+	require.InDelta(t, initialSessionsTotal+2, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
+}
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1652764543 0
#      Tue May 17 05:15:43 2022 +0000
# Node ID 6604d04ecf6592621356721508f7337b190fd602
# Parent  69a1d91856762023270fdc879e015f95444acb2a
# Parent  7a17aa4c8548a037a05e4f60d2d0095bc1a46485
Merge branch 'id-wait-until-gitaly-execution' into 'main'

Wait until all Gitaly sessions are executed

See merge request gitlab-org/gitlab-shell!624

diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -39,7 +39,7 @@
 	require.NoError(t, err)
 
 	var actualNames []string
-	for _, m := range ms[0:9] {
+	for _, m := range ms[0:10] {
 		actualNames = append(actualNames, m.GetName())
 	}
 
@@ -47,6 +47,7 @@
 		"gitlab_shell_http_in_flight_requests",
 		"gitlab_shell_http_request_duration_seconds",
 		"gitlab_shell_http_requests_total",
+		"gitlab_shell_sshd_canceled_sessions",
 		"gitlab_shell_sshd_concurrent_limited_sessions_total",
 		"gitlab_shell_sshd_in_flight_connections",
 		"gitlab_shell_sshd_session_duration_seconds",
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -22,6 +22,7 @@
 	sshdHitMaxSessionsName                    = "concurrent_limited_sessions_total"
 	sshdSessionDurationSecondsName            = "session_duration_seconds"
 	sshdSessionEstablishedDurationSecondsName = "session_established_duration_seconds"
+	sshdCanceledSessionsName                  = "canceled_sessions"
 
 	sliSshdSessionsTotalName       = "gitlab_sli:shell_sshd_sessions:total"
 	sliSshdSessionsErrorsTotalName = "gitlab_sli:shell_sshd_sessions:errors_total"
@@ -76,6 +77,15 @@
 		},
 	)
 
+	SshdCanceledSessions = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      sshdCanceledSessionsName,
+			Help:      "The number of canceled gitlab-sshd sessions.",
+		},
+	)
+
 	SliSshdSessionsTotal = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Name: sliSshdSessionsTotalName,
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -6,6 +6,8 @@
 
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
@@ -15,19 +17,25 @@
 
 const KeepAliveMsg = "keepalive@openssh.com"
 
+var EOFTimeout = 10 * time.Second
+
 type connection struct {
 	cfg                *config.Config
 	concurrentSessions *semaphore.Weighted
 	remoteAddr         string
 	sconn              *ssh.ServerConn
+	maxSessions        int64
 }
 
-type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request)
+type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request) error
 
 func newConnection(cfg *config.Config, remoteAddr string, sconn *ssh.ServerConn) *connection {
+	maxSessions := cfg.Server.ConcurrentSessionsLimit
+
 	return &connection{
 		cfg:                cfg,
-		concurrentSessions: semaphore.NewWeighted(cfg.Server.ConcurrentSessionsLimit),
+		maxSessions:        maxSessions,
+		concurrentSessions: semaphore.NewWeighted(maxSessions),
 		remoteAddr:         remoteAddr,
 		sconn:              sconn,
 	}
@@ -76,10 +84,27 @@
 				}
 			}()
 
-			handler(ctx, channel, requests)
+			metrics.SliSshdSessionsTotal.Inc()
+			err := handler(ctx, channel, requests)
+			if err != nil {
+				if grpcstatus.Convert(err).Code() == grpccodes.Canceled {
+					metrics.SshdCanceledSessions.Inc()
+				} else {
+					metrics.SliSshdSessionsErrorsTotal.Inc()
+				}
+			}
+
 			ctxlog.Info("connection: handle: done")
 		}()
 	}
+
+	// When a connection has been prematurely closed we block execution until all concurrent sessions are released
+	// in order to allow Gitaly complete the operations and close all the channels gracefully.
+	// If it didn't happen within timeout, we unblock the execution
+	// Related issue: https://gitlab.com/gitlab-org/gitlab-shell/-/issues/563
+	ctx, cancel := context.WithTimeout(ctx, EOFTimeout)
+	defer cancel()
+	c.concurrentSessions.Acquire(ctx, c.maxSessions)
 }
 
 func (c *connection) sendKeepAliveMsg(ctx context.Context, ticker *time.Ticker) {
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -7,10 +7,14 @@
 	"testing"
 	"time"
 
+	"github.com/prometheus/client_golang/prometheus/testutil"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
 
 type rejectCall struct {
@@ -90,7 +94,7 @@
 
 	numSessions := 0
 	require.NotPanics(t, func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 			numSessions += 1
 			close(chans)
 			panic("This is a panic")
@@ -128,8 +132,9 @@
 	defer cancel()
 
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 			<-ctx.Done() // Keep the accepted channel open until the end of the test
+			return nil
 		})
 	}()
 
@@ -142,9 +147,10 @@
 	conn, chans := setup(1, newChannel)
 
 	channelHandled := false
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 		channelHandled = true
 		close(chans)
+		return nil
 	})
 
 	require.True(t, channelHandled)
@@ -160,8 +166,9 @@
 
 	channelHandled := false
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 			channelHandled = true
+			return nil
 		})
 	}()
 
@@ -186,3 +193,33 @@
 
 	require.Eventually(t, func() bool { return KeepAliveMsg == f.SentRequestName() }, time.Second, time.Millisecond)
 }
+
+func TestSessionsMetrics(t *testing.T) {
+	// Unfortunately, there is no working way to reset Counter (not CounterVec)
+	// https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#pkg-index
+	initialSessionsTotal := testutil.ToFloat64(metrics.SliSshdSessionsTotal)
+	initialSessionsErrorTotal := testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal)
+	initialCanceledSessions := testutil.ToFloat64(metrics.SshdCanceledSessions)
+
+	newChannel := &fakeNewChannel{channelType: "session"}
+
+	conn, chans := setup(1, newChannel)
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		close(chans)
+		return errors.New("custom error")
+	})
+
+	require.InDelta(t, initialSessionsTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+	require.InDelta(t, initialCanceledSessions, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
+
+	conn, chans = setup(1, newChannel)
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		close(chans)
+		return grpcstatus.Error(grpccodes.Canceled, "error")
+	})
+
+	require.InDelta(t, initialSessionsTotal+2, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
+}
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -22,7 +22,6 @@
 	channel     ssh.Channel
 	gitlabKeyId string
 	remoteAddr  string
-	success     bool
 
 	// State managed by the session
 	execCmd            string
@@ -42,11 +41,12 @@
 	ExitStatus uint32
 }
 
-func (s *session) handle(ctx context.Context, requests <-chan *ssh.Request) {
+func (s *session) handle(ctx context.Context, requests <-chan *ssh.Request) error {
 	ctxlog := log.ContextLogger(ctx)
 
 	ctxlog.Debug("session: handle: entering request loop")
 
+	var err error
 	for req := range requests {
 		sessionLog := ctxlog.WithFields(log.Fields{
 			"bytesize":   len(req.Payload),
@@ -58,12 +58,14 @@
 		var shouldContinue bool
 		switch req.Type {
 		case "env":
-			shouldContinue = s.handleEnv(ctx, req)
+			shouldContinue, err = s.handleEnv(ctx, req)
 		case "exec":
-			shouldContinue = s.handleExec(ctx, req)
+			shouldContinue, err = s.handleExec(ctx, req)
 		case "shell":
 			shouldContinue = false
-			s.exit(ctx, s.handleShell(ctx, req))
+			var status uint32
+			status, err = s.handleShell(ctx, req)
+			s.exit(ctx, status)
 		default:
 			// Ignore unknown requests but don't terminate the session
 			shouldContinue = true
@@ -84,15 +86,17 @@
 	}
 
 	ctxlog.Debug("session: handle: exiting request loop")
+
+	return err
 }
 
-func (s *session) handleEnv(ctx context.Context, req *ssh.Request) bool {
+func (s *session) handleEnv(ctx context.Context, req *ssh.Request) (bool, error) {
 	var accepted bool
 	var envRequest envRequest
 
 	if err := ssh.Unmarshal(req.Payload, &envRequest); err != nil {
 		log.ContextLogger(ctx).WithError(err).Error("session: handleEnv: failed to unmarshal request")
-		return false
+		return false, err
 	}
 
 	switch envRequest.Name {
@@ -113,23 +117,24 @@
 		ctx, log.Fields{"accepted": accepted, "env_request": envRequest},
 	).Debug("session: handleEnv: processed")
 
-	return true
+	return true, nil
 }
 
-func (s *session) handleExec(ctx context.Context, req *ssh.Request) bool {
+func (s *session) handleExec(ctx context.Context, req *ssh.Request) (bool, error) {
 	var execRequest execRequest
 	if err := ssh.Unmarshal(req.Payload, &execRequest); err != nil {
-		return false
+		return false, err
 	}
 
 	s.execCmd = execRequest.Command
 
-	s.exit(ctx, s.handleShell(ctx, req))
+	status, err := s.handleShell(ctx, req)
+	s.exit(ctx, status)
 
-	return false
+	return false, err
 }
 
-func (s *session) handleShell(ctx context.Context, req *ssh.Request) uint32 {
+func (s *session) handleShell(ctx context.Context, req *ssh.Request) (uint32, error) {
 	ctxlog := log.ContextLogger(ctx)
 
 	if req.WantReply {
@@ -157,7 +162,7 @@
 			s.toStderr(ctx, "Failed to parse command: %v\n", err.Error())
 		}
 		s.toStderr(ctx, "Unknown command: %v\n", s.execCmd)
-		return 128
+		return 128, err
 	}
 
 	cmdName := reflect.TypeOf(cmd).String()
@@ -165,12 +170,12 @@
 
 	if err := cmd.Execute(ctx); err != nil {
 		s.toStderr(ctx, "remote: ERROR: %v\n", err.Error())
-		return 1
+		return 1, err
 	}
 
 	ctxlog.Info("session: handleShell: command executed successfully")
 
-	return 0
+	return 0, nil
 }
 
 func (s *session) toStderr(ctx context.Context, format string, args ...interface{}) {
@@ -183,8 +188,6 @@
 	log.WithContextFields(ctx, log.Fields{"exit_status": status}).Info("session: exit: exiting")
 	req := exitStatusReq{ExitStatus: status}
 
-	s.success = status == 0
-
 	s.channel.CloseWrite()
 	s.channel.SendRequest("exit-status", false, ssh.Marshal(req))
 }
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -3,6 +3,7 @@
 import (
 	"bytes"
 	"context"
+	"errors"
 	"io"
 	"net/http"
 	"testing"
@@ -60,22 +61,26 @@
 	testCases := []struct {
 		desc                    string
 		payload                 []byte
+		expectedErr             error
 		expectedProtocolVersion string
 		expectedResult          bool
 	}{
 		{
 			desc:                    "invalid payload",
 			payload:                 []byte("invalid"),
+			expectedErr:             errors.New("ssh: unmarshal error for field Name of type envRequest"),
 			expectedProtocolVersion: "1",
 			expectedResult:          false,
 		}, {
 			desc:                    "valid payload",
 			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL", Value: "2"}),
+			expectedErr:             nil,
 			expectedProtocolVersion: "2",
 			expectedResult:          true,
 		}, {
 			desc:                    "valid payload with forbidden env var",
 			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL_ENV", Value: "2"}),
+			expectedErr:             nil,
 			expectedProtocolVersion: "1",
 			expectedResult:          true,
 		},
@@ -86,8 +91,11 @@
 			s := &session{gitProtocolVersion: "1"}
 			r := &ssh.Request{Payload: tc.payload}
 
-			require.Equal(t, s.handleEnv(context.Background(), r), tc.expectedResult)
-			require.Equal(t, s.gitProtocolVersion, tc.expectedProtocolVersion)
+			shouldContinue, err := s.handleEnv(context.Background(), r)
+
+			require.Equal(t, tc.expectedErr, err)
+			require.Equal(t, tc.expectedResult, shouldContinue)
+			require.Equal(t, tc.expectedProtocolVersion, s.gitProtocolVersion)
 		})
 	}
 }
@@ -96,23 +104,24 @@
 	testCases := []struct {
 		desc               string
 		payload            []byte
+		expectedErr        error
 		expectedExecCmd    string
 		sentRequestName    string
 		sentRequestPayload []byte
-		success            bool
 	}{
 		{
 			desc:            "invalid payload",
 			payload:         []byte("invalid"),
+			expectedErr:     errors.New("ssh: unmarshal error for field Command of type execRequest"),
 			expectedExecCmd: "",
 			sentRequestName: "",
 		}, {
 			desc:               "valid payload",
 			payload:            ssh.Marshal(execRequest{Command: "discover"}),
+			expectedErr:        nil,
 			expectedExecCmd:    "discover",
 			sentRequestName:    "exit-status",
 			sentRequestPayload: ssh.Marshal(exitStatusReq{ExitStatus: 0}),
-			success:            true,
 		},
 	}
 
@@ -129,47 +138,53 @@
 			}
 			r := &ssh.Request{Payload: tc.payload}
 
-			require.Equal(t, false, s.handleExec(context.Background(), r))
+			shouldContinue, err := s.handleExec(context.Background(), r)
+
+			require.Equal(t, tc.expectedErr, err)
+			require.Equal(t, false, shouldContinue)
 			require.Equal(t, tc.sentRequestName, f.sentRequestName)
 			require.Equal(t, tc.sentRequestPayload, f.sentRequestPayload)
-			require.Equal(t, tc.success, s.success)
 		})
 	}
 }
 
 func TestHandleShell(t *testing.T) {
 	testCases := []struct {
-		desc             string
-		cmd              string
-		errMsg           string
-		gitlabKeyId      string
-		expectedExitCode uint32
-		success          bool
+		desc              string
+		cmd               string
+		errMsg            string
+		gitlabKeyId       string
+		expectedErrString string
+		expectedExitCode  uint32
 	}{
 		{
-			desc:             "fails to parse command",
-			cmd:              `\`,
-			errMsg:           "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
-			gitlabKeyId:      "root",
-			expectedExitCode: 128,
+			desc:              "fails to parse command",
+			cmd:               `\`,
+			errMsg:            "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
+			gitlabKeyId:       "root",
+			expectedErrString: "Invalid SSH command: invalid command line string",
+			expectedExitCode:  128,
 		}, {
-			desc:             "specified command is unknown",
-			cmd:              "unknown-command",
-			errMsg:           "Unknown command: unknown-command\n",
-			gitlabKeyId:      "root",
-			expectedExitCode: 128,
+			desc:              "specified command is unknown",
+			cmd:               "unknown-command",
+			errMsg:            "Unknown command: unknown-command\n",
+			gitlabKeyId:       "root",
+			expectedErrString: "Disallowed command",
+			expectedExitCode:  128,
 		}, {
-			desc:             "fails to parse command",
-			cmd:              "discover",
-			gitlabKeyId:      "",
-			errMsg:           "remote: ERROR: Failed to get username: who='' is invalid\n",
-			expectedExitCode: 1,
+			desc:              "fails to parse command",
+			cmd:               "discover",
+			gitlabKeyId:       "",
+			errMsg:            "remote: ERROR: Failed to get username: who='' is invalid\n",
+			expectedErrString: "Failed to get username: who='' is invalid",
+			expectedExitCode:  1,
 		}, {
-			desc:             "fails to parse command",
-			cmd:              "discover",
-			errMsg:           "",
-			gitlabKeyId:      "root",
-			expectedExitCode: 0,
+			desc:              "fails to parse command",
+			cmd:               "discover",
+			errMsg:            "",
+			gitlabKeyId:       "root",
+			expectedErrString: "",
+			expectedExitCode:  0,
 		},
 	}
 
@@ -186,7 +201,13 @@
 			}
 			r := &ssh.Request{}
 
-			require.Equal(t, tc.expectedExitCode, s.handleShell(context.Background(), r))
+			exitCode, err := s.handleShell(context.Background(), r)
+
+			if tc.expectedErrString != "" {
+				require.Equal(t, tc.expectedErrString, err.Error())
+			}
+
+			require.Equal(t, tc.expectedExitCode, exitCode)
 			require.Equal(t, tc.errMsg, out.String())
 		})
 	}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -181,7 +181,7 @@
 	started := time.Now()
 	var establishSessionDuration float64
 	conn := newConnection(s.Config, remoteAddr, sconn)
-	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
+	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) error {
 		establishSessionDuration = time.Since(started).Seconds()
 		metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
 
@@ -192,11 +192,7 @@
 			remoteAddr:  remoteAddr,
 		}
 
-		metrics.SliSshdSessionsTotal.Inc()
-		session.handle(ctx, requests)
-		if !session.success {
-			metrics.SliSshdSessionsErrorsTotal.Inc()
-		}
+		return session.handle(ctx, requests)
 	})
 
 	reason := sconn.Wait()
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652764523 -14400
#      Tue May 17 09:15:23 2022 +0400
# Node ID 55cc92a5c0c755a56eb60915b61866be19953812
# Parent  6604d04ecf6592621356721508f7337b190fd602
Release v14.3.0

- Remove deprecated bundler-audit !626
- Wait until all Gitaly sessions are executed !624

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v14.3.0
+
+- Remove deprecated bundler-audit !626
+- Wait until all Gitaly sessions are executed !624
+
 v14.2.0
 
 - Implement ClientKeepAlive option !622
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.2.0
+14.3.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652765061 0
#      Tue May 17 05:24:21 2022 +0000
# Node ID c7cb78872f0da7b8083d01a00da0f1c478ee42e3
# Parent  6604d04ecf6592621356721508f7337b190fd602
# Parent  55cc92a5c0c755a56eb60915b61866be19953812
Merge branch 'id-release-14-3-0' into 'main'

Release v14.3.0

See merge request gitlab-org/gitlab-shell!627

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v14.3.0
+
+- Remove deprecated bundler-audit !626
+- Wait until all Gitaly sessions are executed !624
+
 v14.2.0
 
 - Implement ClientKeepAlive option !622
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.2.0
+14.3.0
# HG changeset patch
# User Costel Maxim <cmaxim@gitlab.com>
# Date 1652798396 0
#      Tue May 17 14:39:56 2022 +0000
# Node ID 7dfcbf548920a0081247253cea142b54b81ce42f
# Parent  c7cb78872f0da7b8083d01a00da0f1c478ee42e3
Resolve "Dependency update DOCKER_VERSION:  20.10.15"

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,7 +8,7 @@
       - '/ci/danger-review.yml'
 
 variables:
-  DOCKER_VERSION: "20.10.3"
+  DOCKER_VERSION: "20.10.15"
 
 workflow:
   rules: &workflow_rules
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652798397 0
#      Tue May 17 14:39:57 2022 +0000
# Node ID e5060a36dafed0a2fb0fcdd3bd084f6cacd8c393
# Parent  c7cb78872f0da7b8083d01a00da0f1c478ee42e3
# Parent  7dfcbf548920a0081247253cea142b54b81ce42f
Merge branch '571-dependency-update-docker_version-20-10-15' into 'main'

Resolve "Dependency update DOCKER_VERSION:  20.10.15"

Closes #571

See merge request gitlab-org/gitlab-shell!628

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,7 +8,7 @@
       - '/ci/danger-review.yml'
 
 variables:
-  DOCKER_VERSION: "20.10.3"
+  DOCKER_VERSION: "20.10.15"
 
 workflow:
   rules: &workflow_rules
# HG changeset patch
# User Sean Carroll <scarroll@gitlab.com>
# Date 1652866142 -7200
#      Wed May 18 11:29:02 2022 +0200
# Node ID 6ba21e8902c1b95e8fcc9f2b732e28365fb26044
# Parent  e5060a36dafed0a2fb0fcdd3bd084f6cacd8c393
Git ignore .DS_Store

diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,4 @@
 tags
 tmp/*
 vendor
+.DS_Store
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652869843 0
#      Wed May 18 10:30:43 2022 +0000
# Node ID 0e062368747082f13655ddebf21c03968e7d8727
# Parent  e5060a36dafed0a2fb0fcdd3bd084f6cacd8c393
# Parent  6ba21e8902c1b95e8fcc9f2b732e28365fb26044
Merge branch 'ds-store' into 'main'

Git ignore .DS_Store

See merge request gitlab-org/gitlab-shell!629

diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,4 @@
 tags
 tmp/*
 vendor
+.DS_Store
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652881192 -14400
#      Wed May 18 17:39:52 2022 +0400
# Node ID f8a45bbc6d510998d0caf3c58d9ca4ca97394acc
# Parent  c7cb78872f0da7b8083d01a00da0f1c478ee42e3
Exclude API errors from error rate

When API isn't responsible or the resource is not accessible
(returns 404 or 403), then we shouldn't consider it as an error
on gitlab-sshd side

diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -37,6 +37,14 @@
 	userAgent  string
 }
 
+type ApiError struct {
+	Msg string
+}
+
+func (e *ApiError) Error() string {
+	return e.Msg
+}
+
 func NewGitlabNetClient(
 	user,
 	password,
@@ -101,9 +109,9 @@
 	parsedResponse := &ErrorResponse{}
 
 	if err := json.NewDecoder(resp.Body).Decode(parsedResponse); err != nil {
-		return fmt.Errorf("Internal API error (%v)", resp.StatusCode)
+		return &ApiError{fmt.Sprintf("Internal API error (%v)", resp.StatusCode)}
 	} else {
-		return fmt.Errorf(parsedResponse.Message)
+		return &ApiError{parsedResponse.Message}
 	}
 
 }
@@ -157,7 +165,7 @@
 
 	if err != nil {
 		logger.WithError(err).Error("Internal API unreachable")
-		return nil, fmt.Errorf("Internal API unreachable")
+		return nil, &ApiError{"Internal API unreachable"}
 	}
 
 	if response != nil {
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -2,6 +2,7 @@
 
 import (
 	"context"
+	"errors"
 	"time"
 
 	"golang.org/x/crypto/ssh"
@@ -9,6 +10,7 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
+	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
@@ -90,7 +92,10 @@
 				if grpcstatus.Convert(err).Code() == grpccodes.Canceled {
 					metrics.SshdCanceledSessions.Inc()
 				} else {
-					metrics.SliSshdSessionsErrorsTotal.Inc()
+					var apiError *client.ApiError
+					if !errors.As(err, &apiError) {
+						metrics.SliSshdSessionsErrorsTotal.Inc()
+					}
 				}
 			}
 
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -13,6 +13,7 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
+	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
@@ -222,4 +223,14 @@
 	require.InDelta(t, initialSessionsTotal+2, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
+
+	conn, chans = setup(1, newChannel)
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		close(chans)
+		return &client.ApiError{"api error"}
+	})
+
+	require.InDelta(t, initialSessionsTotal+3, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
 }
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1652883067 0
#      Wed May 18 14:11:07 2022 +0000
# Node ID db75b36effa4d6e4da4430be795b9d6eec9c2869
# Parent  0e062368747082f13655ddebf21c03968e7d8727
# Parent  f8a45bbc6d510998d0caf3c58d9ca4ca97394acc
Merge branch 'id-ignore-api-errors' into 'main'

Exclude API errors from error rate

See merge request gitlab-org/gitlab-shell!630

diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -37,6 +37,14 @@
 	userAgent  string
 }
 
+type ApiError struct {
+	Msg string
+}
+
+func (e *ApiError) Error() string {
+	return e.Msg
+}
+
 func NewGitlabNetClient(
 	user,
 	password,
@@ -101,9 +109,9 @@
 	parsedResponse := &ErrorResponse{}
 
 	if err := json.NewDecoder(resp.Body).Decode(parsedResponse); err != nil {
-		return fmt.Errorf("Internal API error (%v)", resp.StatusCode)
+		return &ApiError{fmt.Sprintf("Internal API error (%v)", resp.StatusCode)}
 	} else {
-		return fmt.Errorf(parsedResponse.Message)
+		return &ApiError{parsedResponse.Message}
 	}
 
 }
@@ -157,7 +165,7 @@
 
 	if err != nil {
 		logger.WithError(err).Error("Internal API unreachable")
-		return nil, fmt.Errorf("Internal API unreachable")
+		return nil, &ApiError{"Internal API unreachable"}
 	}
 
 	if response != nil {
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -2,6 +2,7 @@
 
 import (
 	"context"
+	"errors"
 	"time"
 
 	"golang.org/x/crypto/ssh"
@@ -9,6 +10,7 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
+	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
@@ -90,7 +92,10 @@
 				if grpcstatus.Convert(err).Code() == grpccodes.Canceled {
 					metrics.SshdCanceledSessions.Inc()
 				} else {
-					metrics.SliSshdSessionsErrorsTotal.Inc()
+					var apiError *client.ApiError
+					if !errors.As(err, &apiError) {
+						metrics.SliSshdSessionsErrorsTotal.Inc()
+					}
 				}
 			}
 
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -13,6 +13,7 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
+	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
@@ -222,4 +223,14 @@
 	require.InDelta(t, initialSessionsTotal+2, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
+
+	conn, chans = setup(1, newChannel)
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		close(chans)
+		return &client.ApiError{"api error"}
+	})
+
+	require.InDelta(t, initialSessionsTotal+3, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
 }
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652883182 -14400
#      Wed May 18 18:13:02 2022 +0400
# Node ID 866b7fbfc6ca08f99d852bbc742a275489c683d9
# Parent  db75b36effa4d6e4da4430be795b9d6eec9c2869
Release v14.3.1

- Exclude API errors from error rate !630

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.3.1
+
+- Exclude API errors from error rate !630
+
 v14.3.0
 
 - Remove deprecated bundler-audit !626
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.3.0
+14.3.1
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652884757 0
#      Wed May 18 14:39:17 2022 +0000
# Node ID 0ea5c918a3bf8342b43bb8ec5e616bec564f41f4
# Parent  db75b36effa4d6e4da4430be795b9d6eec9c2869
# Parent  866b7fbfc6ca08f99d852bbc742a275489c683d9
Merge branch 'id-release-14-3-1' into 'main'

Release v14.3.1

See merge request gitlab-org/gitlab-shell!631

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.3.1
+
+- Exclude API errors from error rate !630
+
 v14.3.0
 
 - Remove deprecated bundler-audit !626
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.3.0
+14.3.1
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1652907213 25200
#      Wed May 18 13:53:33 2022 -0700
# Node ID 4c592bff8162f6bdd2dbfb4ae0624c81a8b221d6
# Parent  0ea5c918a3bf8342b43bb8ec5e616bec564f41f4
Update gitlab-org/golang-crypto module version

This update pulls in:

1. https://gitlab.com/gitlab-org/golang-crypto/-/merge_requests/3,
   which syncs the module with upstream master and supports the new
   `curve25519-sha256@libssh.org` kex name.

2. https://gitlab.com/gitlab-org/golang-crypto/-/merge_requests/4,
   which adds:

   * MACs: hmac-sha2-512-etm@openssh.com, hmac-sha2-512
   * Cipher: aes256-gcm@openssh.com

Relates to https://gitlab.com/gitlab-org/gitlab-shell/-/issues/575

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -81,4 +81,4 @@
 	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
 )
 
-replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80
+replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -888,8 +888,8 @@
 gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
-gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80 h1:rVzTLlEDlX/ocWqNxeuxb9XklLzmI9FdP0teRqkSg00=
-gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac h1:qNUzqBTbEGGjF5Fp0NWz3rNmqamwchxM+QKUZYeOS1c=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652908785 0
#      Wed May 18 21:19:45 2022 +0000
# Node ID 2a2ce010683a031623595b8ed3e740653d7f2057
# Parent  0ea5c918a3bf8342b43bb8ec5e616bec564f41f4
# Parent  4c592bff8162f6bdd2dbfb4ae0624c81a8b221d6
Merge branch 'sh-update-crypto-ver' into 'main'

Update gitlab-org/golang-crypto module version

See merge request gitlab-org/gitlab-shell!632

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -81,4 +81,4 @@
 	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
 )
 
-replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80
+replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -888,8 +888,8 @@
 gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
-gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80 h1:rVzTLlEDlX/ocWqNxeuxb9XklLzmI9FdP0teRqkSg00=
-gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac h1:qNUzqBTbEGGjF5Fp0NWz3rNmqamwchxM+QKUZYeOS1c=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652910576 -14400
#      Thu May 19 01:49:36 2022 +0400
# Node ID 30df69d888ae6cdfff2057f321b437f8e36b5383
# Parent  2a2ce010683a031623595b8ed3e740653d7f2057
Allow configuring SSH server algorithms

MACs, Ciphers and KEX algorithms now can be configured
If the values are empty, reasonable defaults are used

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -84,6 +84,12 @@
   readiness_probe: "/start"
   # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
   liveness_probe: "/health"
+  # Specifies the available message authentication code algorithms that are used for protecting data integrity
+  macs: [hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1]
+  # Specifies the available Key Exchange algorithms
+  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1]
+  # Specified the ciphers allowed
+  ciphers: [aes128-gcm@openssh.com, chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-ctr, aes192-ctr,aes256-ctr]
   # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -32,6 +32,9 @@
 	ReadinessProbe             string   `yaml:"readiness_probe"`
 	LivenessProbe              string   `yaml:"liveness_probe"`
 	HostKeyFiles               []string `yaml:"host_key_files,omitempty"`
+	MACs                       []string `yaml:"macs"`
+	KexAlgorithms              []string `yaml:"kex_algorithms"`
+	Ciphers                    []string `yaml:"ciphers"`
 }
 
 type HttpSettingsConfig struct {
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -16,6 +16,14 @@
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
+var supportedMACs = []string{
+	"hmac-sha2-256-etm@openssh.com",
+	"hmac-sha2-512-etm@openssh.com",
+	"hmac-sha2-256",
+	"hmac-sha2-512",
+	"hmac-sha1",
+}
+
 type serverConfig struct {
 	cfg                  *config.Config
 	hostKeys             []ssh.Signer
@@ -86,6 +94,20 @@
 		},
 	}
 
+	if len(s.cfg.Server.MACs) > 0 {
+		sshCfg.MACs = s.cfg.Server.MACs
+	} else {
+		sshCfg.MACs = supportedMACs
+	}
+
+	if len(s.cfg.Server.KexAlgorithms) > 0 {
+		sshCfg.KeyExchanges = s.cfg.Server.KexAlgorithms
+	}
+
+	if len(s.cfg.Server.Ciphers) > 0 {
+		sshCfg.Ciphers = s.cfg.Server.Ciphers
+	}
+
 	for _, key := range s.hostKeys {
 		sshCfg.AddHostKey(key)
 	}
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
--- a/internal/sshd/server_config_test.go
+++ b/internal/sshd/server_config_test.go
@@ -80,6 +80,67 @@
 	}
 }
 
+func TestDefaultAlgorithms(t *testing.T) {
+	srvCfg := &serverConfig{cfg: &config.Config{}}
+	sshServerConfig := srvCfg.get(context.Background())
+
+	require.Equal(t, supportedMACs, sshServerConfig.MACs)
+	require.Nil(t, sshServerConfig.KeyExchanges)
+	require.Nil(t, sshServerConfig.Ciphers)
+
+	sshServerConfig.SetDefaults()
+
+	require.Equal(t, supportedMACs, sshServerConfig.MACs)
+
+	defaultKeyExchanges := []string{
+		"curve25519-sha256",
+		"curve25519-sha256@libssh.org",
+		"ecdh-sha2-nistp256",
+		"ecdh-sha2-nistp384",
+		"ecdh-sha2-nistp521",
+		"diffie-hellman-group14-sha256",
+		"diffie-hellman-group14-sha1",
+	}
+	require.Equal(t, defaultKeyExchanges, sshServerConfig.KeyExchanges)
+
+	defaultCiphers := []string{
+		"aes128-gcm@openssh.com",
+		"chacha20-poly1305@openssh.com",
+		"aes256-gcm@openssh.com",
+		"aes128-ctr",
+		"aes192-ctr",
+		"aes256-ctr",
+	}
+	require.Equal(t, defaultCiphers, sshServerConfig.Ciphers)
+}
+
+func TestCustomAlgorithms(t *testing.T) {
+	customMACs := []string{"hmac-sha2-512-etm@openssh.com"}
+	customKexAlgos := []string{"curve25519-sha256"}
+	customCiphers := []string{"aes256-gcm@openssh.com"}
+
+	srvCfg := &serverConfig{
+		cfg: &config.Config{
+			Server: config.ServerConfig{
+				MACs:          customMACs,
+				KexAlgorithms: customKexAlgos,
+				Ciphers:       customCiphers,
+			},
+		},
+	}
+	sshServerConfig := srvCfg.get(context.Background())
+
+	require.Equal(t, customMACs, sshServerConfig.MACs)
+	require.Equal(t, customKexAlgos, sshServerConfig.KeyExchanges)
+	require.Equal(t, customCiphers, sshServerConfig.Ciphers)
+
+	sshServerConfig.SetDefaults()
+
+	require.Equal(t, customMACs, sshServerConfig.MACs)
+	require.Equal(t, customKexAlgos, sshServerConfig.KeyExchanges)
+	require.Equal(t, customCiphers, sshServerConfig.Ciphers)
+}
+
 func rsaPublicKey(t *testing.T) ssh.PublicKey {
 	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
 	require.NoError(t, err)
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1652914523 0
#      Wed May 18 22:55:23 2022 +0000
# Node ID 94ce2390d9e30cc09306a560ef89d55b376e410a
# Parent  2a2ce010683a031623595b8ed3e740653d7f2057
# Parent  30df69d888ae6cdfff2057f321b437f8e36b5383
Merge branch 'id-configure-algorithms' into 'main'

Allow configuring SSH server algorithms

See merge request gitlab-org/gitlab-shell!633

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -84,6 +84,12 @@
   readiness_probe: "/start"
   # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
   liveness_probe: "/health"
+  # Specifies the available message authentication code algorithms that are used for protecting data integrity
+  macs: [hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1]
+  # Specifies the available Key Exchange algorithms
+  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1]
+  # Specified the ciphers allowed
+  ciphers: [aes128-gcm@openssh.com, chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-ctr, aes192-ctr,aes256-ctr]
   # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -32,6 +32,9 @@
 	ReadinessProbe             string   `yaml:"readiness_probe"`
 	LivenessProbe              string   `yaml:"liveness_probe"`
 	HostKeyFiles               []string `yaml:"host_key_files,omitempty"`
+	MACs                       []string `yaml:"macs"`
+	KexAlgorithms              []string `yaml:"kex_algorithms"`
+	Ciphers                    []string `yaml:"ciphers"`
 }
 
 type HttpSettingsConfig struct {
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -16,6 +16,14 @@
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
+var supportedMACs = []string{
+	"hmac-sha2-256-etm@openssh.com",
+	"hmac-sha2-512-etm@openssh.com",
+	"hmac-sha2-256",
+	"hmac-sha2-512",
+	"hmac-sha1",
+}
+
 type serverConfig struct {
 	cfg                  *config.Config
 	hostKeys             []ssh.Signer
@@ -86,6 +94,20 @@
 		},
 	}
 
+	if len(s.cfg.Server.MACs) > 0 {
+		sshCfg.MACs = s.cfg.Server.MACs
+	} else {
+		sshCfg.MACs = supportedMACs
+	}
+
+	if len(s.cfg.Server.KexAlgorithms) > 0 {
+		sshCfg.KeyExchanges = s.cfg.Server.KexAlgorithms
+	}
+
+	if len(s.cfg.Server.Ciphers) > 0 {
+		sshCfg.Ciphers = s.cfg.Server.Ciphers
+	}
+
 	for _, key := range s.hostKeys {
 		sshCfg.AddHostKey(key)
 	}
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
--- a/internal/sshd/server_config_test.go
+++ b/internal/sshd/server_config_test.go
@@ -80,6 +80,67 @@
 	}
 }
 
+func TestDefaultAlgorithms(t *testing.T) {
+	srvCfg := &serverConfig{cfg: &config.Config{}}
+	sshServerConfig := srvCfg.get(context.Background())
+
+	require.Equal(t, supportedMACs, sshServerConfig.MACs)
+	require.Nil(t, sshServerConfig.KeyExchanges)
+	require.Nil(t, sshServerConfig.Ciphers)
+
+	sshServerConfig.SetDefaults()
+
+	require.Equal(t, supportedMACs, sshServerConfig.MACs)
+
+	defaultKeyExchanges := []string{
+		"curve25519-sha256",
+		"curve25519-sha256@libssh.org",
+		"ecdh-sha2-nistp256",
+		"ecdh-sha2-nistp384",
+		"ecdh-sha2-nistp521",
+		"diffie-hellman-group14-sha256",
+		"diffie-hellman-group14-sha1",
+	}
+	require.Equal(t, defaultKeyExchanges, sshServerConfig.KeyExchanges)
+
+	defaultCiphers := []string{
+		"aes128-gcm@openssh.com",
+		"chacha20-poly1305@openssh.com",
+		"aes256-gcm@openssh.com",
+		"aes128-ctr",
+		"aes192-ctr",
+		"aes256-ctr",
+	}
+	require.Equal(t, defaultCiphers, sshServerConfig.Ciphers)
+}
+
+func TestCustomAlgorithms(t *testing.T) {
+	customMACs := []string{"hmac-sha2-512-etm@openssh.com"}
+	customKexAlgos := []string{"curve25519-sha256"}
+	customCiphers := []string{"aes256-gcm@openssh.com"}
+
+	srvCfg := &serverConfig{
+		cfg: &config.Config{
+			Server: config.ServerConfig{
+				MACs:          customMACs,
+				KexAlgorithms: customKexAlgos,
+				Ciphers:       customCiphers,
+			},
+		},
+	}
+	sshServerConfig := srvCfg.get(context.Background())
+
+	require.Equal(t, customMACs, sshServerConfig.MACs)
+	require.Equal(t, customKexAlgos, sshServerConfig.KeyExchanges)
+	require.Equal(t, customCiphers, sshServerConfig.Ciphers)
+
+	sshServerConfig.SetDefaults()
+
+	require.Equal(t, customMACs, sshServerConfig.MACs)
+	require.Equal(t, customKexAlgos, sshServerConfig.KeyExchanges)
+	require.Equal(t, customCiphers, sshServerConfig.Ciphers)
+}
+
 func rsaPublicKey(t *testing.T) ssh.PublicKey {
 	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
 	require.NoError(t, err)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652914696 -14400
#      Thu May 19 02:58:16 2022 +0400
# Node ID b76642ca4efcde8a23116aed75a56594c1504dd9
# Parent  94ce2390d9e30cc09306a560ef89d55b376e410a
Release 14.4.0

- Allow configuring SSH server algorithms !633
- Update gitlab-org/golang-crypto module version !632

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v14.4.0
+
+- Allow configuring SSH server algorithms !633
+- Update gitlab-org/golang-crypto module version !632
+
 v14.3.1
 
 - Exclude API errors from error rate !630
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.3.1
+14.4.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652915139 0
#      Wed May 18 23:05:39 2022 +0000
# Node ID b5a0002e1a3887d92469d212c04d461bda32c6ef
# Parent  94ce2390d9e30cc09306a560ef89d55b376e410a
# Parent  b76642ca4efcde8a23116aed75a56594c1504dd9
Merge branch 'id-release-14-4-0' into 'main'

Release 14.4.0

See merge request gitlab-org/gitlab-shell!634

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v14.4.0
+
+- Allow configuring SSH server algorithms !633
+- Update gitlab-org/golang-crypto module version !632
+
 v14.3.1
 
 - Exclude API errors from error rate !630
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.3.1
+14.4.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652951065 -14400
#      Thu May 19 13:04:25 2022 +0400
# Node ID 3a4948692b5d655284883e7d173f4b2a7174f683
# Parent  b5a0002e1a3887d92469d212c04d461bda32c6ef
Allow specifying formatted durations in config

- If an integer is specified, we assume that these are seconds
- A duration of format "500ms", "10s", "1m", etc... accepted

diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -99,11 +99,12 @@
 		sig := <-done
 		signal.Reset(syscall.SIGINT, syscall.SIGTERM)
 
-		log.WithContextFields(ctx, log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
+		gracePeriod := time.Duration(cfg.Server.GracePeriod)
+		log.WithContextFields(ctx, log.Fields{"shutdown_timeout_s": gracePeriod.Seconds(), "signal": sig.String()}).Info("Shutdown initiated")
 
 		server.Shutdown()
 
-		<-time.After(cfg.Server.GracePeriod())
+		<-time.After(gracePeriod)
 
 		cancel()
 
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -21,20 +21,22 @@
 	defaultSecretFileName = ".gitlab_shell_secret"
 )
 
+type yamlDuration time.Duration
+
 type ServerConfig struct {
-	Listen                     string   `yaml:"listen,omitempty"`
-	ProxyProtocol              bool     `yaml:"proxy_protocol,omitempty"`
-	ProxyPolicy                string   `yaml:"proxy_policy,omitempty"`
-	WebListen                  string   `yaml:"web_listen,omitempty"`
-	ConcurrentSessionsLimit    int64    `yaml:"concurrent_sessions_limit,omitempty"`
-	ClientAliveIntervalSeconds int64    `yaml:"client_alive_interval,omitempty"`
-	GracePeriodSeconds         uint64   `yaml:"grace_period"`
-	ReadinessProbe             string   `yaml:"readiness_probe"`
-	LivenessProbe              string   `yaml:"liveness_probe"`
-	HostKeyFiles               []string `yaml:"host_key_files,omitempty"`
-	MACs                       []string `yaml:"macs"`
-	KexAlgorithms              []string `yaml:"kex_algorithms"`
-	Ciphers                    []string `yaml:"ciphers"`
+	Listen                  string       `yaml:"listen,omitempty"`
+	ProxyProtocol           bool         `yaml:"proxy_protocol,omitempty"`
+	ProxyPolicy             string       `yaml:"proxy_policy,omitempty"`
+	WebListen               string       `yaml:"web_listen,omitempty"`
+	ConcurrentSessionsLimit int64        `yaml:"concurrent_sessions_limit,omitempty"`
+	ClientAliveInterval     yamlDuration `yaml:"client_alive_interval,omitempty"`
+	GracePeriod             yamlDuration `yaml:"grace_period"`
+	ReadinessProbe          string       `yaml:"readiness_probe"`
+	LivenessProbe           string       `yaml:"liveness_probe"`
+	HostKeyFiles            []string     `yaml:"host_key_files,omitempty"`
+	MACs                    []string     `yaml:"macs"`
+	KexAlgorithms           []string     `yaml:"kex_algorithms"`
+	Ciphers                 []string     `yaml:"ciphers"`
 }
 
 type HttpSettingsConfig struct {
@@ -79,13 +81,13 @@
 	}
 
 	DefaultServerConfig = ServerConfig{
-		Listen:                     "[::]:22",
-		WebListen:                  "localhost:9122",
-		ConcurrentSessionsLimit:    10,
-		GracePeriodSeconds:         10,
-		ClientAliveIntervalSeconds: 15,
-		ReadinessProbe:             "/start",
-		LivenessProbe:              "/health",
+		Listen:                  "[::]:22",
+		WebListen:               "localhost:9122",
+		ConcurrentSessionsLimit: 10,
+		GracePeriod:             yamlDuration(10 * time.Second),
+		ClientAliveInterval:     yamlDuration(15 * time.Second),
+		ReadinessProbe:          "/start",
+		LivenessProbe:           "/health",
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
@@ -94,12 +96,15 @@
 	}
 )
 
-func (sc *ServerConfig) ClientAliveInterval() time.Duration {
-	return time.Duration(sc.ClientAliveIntervalSeconds) * time.Second
-}
+func (d *yamlDuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	var intDuration int
+	if err := unmarshal(&intDuration); err != nil {
+		return unmarshal((*time.Duration)(d))
+	}
 
-func (sc *ServerConfig) GracePeriod() time.Duration {
-	return time.Duration(sc.GracePeriodSeconds) * time.Second
+	*d = yamlDuration(time.Duration(intDuration) * time.Second)
+
+	return nil
 }
 
 func (c *Config) ApplyGlobalState() {
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -3,7 +3,9 @@
 import (
 	"os"
 	"testing"
+	"time"
 
+	yaml "gopkg.in/yaml.v2"
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/stretchr/testify/require"
 
@@ -58,3 +60,39 @@
 
 	require.Equal(t, expectedMetricNames, actualNames)
 }
+
+func TestNewFromDir(t *testing.T) {
+	testhelper.PrepareTestRootDir(t)
+
+	cfg, err := NewFromDir(testhelper.TestRoot)
+	require.NoError(t, err)
+
+	require.Equal(t, 10 * time.Second, time.Duration(cfg.Server.GracePeriod))
+	require.Equal(t, 1 * time.Minute, time.Duration(cfg.Server.ClientAliveInterval))
+}
+
+func TestYAMLDuration(t *testing.T) {
+	testCases := []struct{
+		desc string
+		data string
+		duration time.Duration
+	}{
+		{"seconds assumed by default", "duration: 10", 10 * time.Second},
+		{"milliseconds are parsed", "duration: 500ms", 500 * time.Millisecond},
+		{"minutes are parsed", "duration: 1m", 1 * time.Minute},
+	}
+
+	type durationCfg struct {
+		Duration yamlDuration `yaml:"duration"`
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			var cfg durationCfg
+			err := yaml.Unmarshal([]byte(tc.data), &cfg)
+			require.NoError(t, err)
+
+			require.Equal(t, tc.duration, time.Duration(cfg.Duration))
+		})
+	}
+}
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -46,8 +46,8 @@
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
-	if c.cfg.Server.ClientAliveIntervalSeconds > 0 {
-		ticker := time.NewTicker(c.cfg.Server.ClientAliveInterval())
+	if c.cfg.Server.ClientAliveInterval > 0 {
+		ticker := time.NewTicker(time.Duration(c.cfg.Server.ClientAliveInterval))
 		defer ticker.Stop()
 		go c.sendKeepAliveMsg(ctx, ticker)
 	}
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -80,7 +80,7 @@
 }
 
 func setup(sessionsNum int64, newChannel *fakeNewChannel) (*connection, chan ssh.NewChannel) {
-	cfg := &config.Config{Server: config.ServerConfig{ConcurrentSessionsLimit: sessionsNum, ClientAliveIntervalSeconds: 1}}
+	cfg := &config.Config{Server: config.ServerConfig{ConcurrentSessionsLimit: sessionsNum}}
 	conn := newConnection(cfg, "127.0.0.1:50000", &ssh.ServerConn{&fakeConn{}, nil})
 
 	chans := make(chan ssh.NewChannel, 1)
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -265,7 +265,6 @@
 	cfg.User = user
 	cfg.Server.Listen = serverUrl
 	cfg.Server.ConcurrentSessionsLimit = 1
-	cfg.Server.ClientAliveIntervalSeconds = 15
 	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
 
 	s, err := NewServer(cfg)
diff --git a/internal/testhelper/testdata/testroot/config.yml b/internal/testhelper/testdata/testroot/config.yml
--- a/internal/testhelper/testdata/testroot/config.yml
+++ b/internal/testhelper/testdata/testroot/config.yml
@@ -0,0 +1,3 @@
+sshd:
+  grace_period: 10
+  client_alive_interval: 1m
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1652951414 -14400
#      Thu May 19 13:10:14 2022 +0400
# Node ID 615e6c9ba39d727a4f4941c36a00aa83011bacd6
# Parent  3a4948692b5d655284883e7d173f4b2a7174f683
Make ProxyHeaderTimeout configurable

Issue: https://gitlab.com/gitlab-org/gitlab-shell/-/issues/576

ProxyHeaderTimeout must be small to avoid DoS risk

Let's make the value configurable and 500ms by default

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -80,6 +80,8 @@
   client_alive_interval: 15
   # The server waits for this time (in seconds) for the ongoing connections to complete before shutting down. Defaults to 10.
   grace_period: 10
+  # A short timeout to decide to abort the connection if the protocol header is not seen within it. Defaults to 500ms
+  proxy_header_timeout: 500ms
   # The endpoint that returns 200 OK if the server is ready to receive incoming connections; otherwise, it returns 503 Service Unavailable. Defaults to "/start".
   readiness_probe: "/start"
   # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -31,6 +31,7 @@
 	ConcurrentSessionsLimit int64        `yaml:"concurrent_sessions_limit,omitempty"`
 	ClientAliveInterval     yamlDuration `yaml:"client_alive_interval,omitempty"`
 	GracePeriod             yamlDuration `yaml:"grace_period"`
+	ProxyHeaderTimeout      yamlDuration `yaml:"proxy_header_timeout"`
 	ReadinessProbe          string       `yaml:"readiness_probe"`
 	LivenessProbe           string       `yaml:"liveness_probe"`
 	HostKeyFiles            []string     `yaml:"host_key_files,omitempty"`
@@ -86,6 +87,7 @@
 		ConcurrentSessionsLimit: 10,
 		GracePeriod:             yamlDuration(10 * time.Second),
 		ClientAliveInterval:     yamlDuration(15 * time.Second),
+		ProxyHeaderTimeout:      yamlDuration(500 * time.Millisecond),
 		ReadinessProbe:          "/start",
 		LivenessProbe:           "/health",
 		HostKeyFiles: []string{
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -5,9 +5,9 @@
 	"testing"
 	"time"
 
-	yaml "gopkg.in/yaml.v2"
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/stretchr/testify/require"
+	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
@@ -67,14 +67,15 @@
 	cfg, err := NewFromDir(testhelper.TestRoot)
 	require.NoError(t, err)
 
-	require.Equal(t, 10 * time.Second, time.Duration(cfg.Server.GracePeriod))
-	require.Equal(t, 1 * time.Minute, time.Duration(cfg.Server.ClientAliveInterval))
+	require.Equal(t, 10*time.Second, time.Duration(cfg.Server.GracePeriod))
+	require.Equal(t, 1*time.Minute, time.Duration(cfg.Server.ClientAliveInterval))
+	require.Equal(t, 500*time.Millisecond, time.Duration(cfg.Server.ProxyHeaderTimeout))
 }
 
 func TestYAMLDuration(t *testing.T) {
-	testCases := []struct{
-		desc string
-		data string
+	testCases := []struct {
+		desc     string
+		data     string
 		duration time.Duration
 	}{
 		{"seconds assumed by default", "duration: 10", 10 * time.Second},
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -26,7 +26,6 @@
 	StatusReady
 	StatusOnShutdown
 	StatusClosed
-	ProxyHeaderTimeout = 90 * time.Second
 )
 
 type Server struct {
@@ -97,7 +96,7 @@
 		sshListener = &proxyproto.Listener{
 			Listener:          sshListener,
 			Policy:            s.requirePolicy,
-			ReadHeaderTimeout: ProxyHeaderTimeout,
+			ReadHeaderTimeout: time.Duration(s.Config.Server.ProxyHeaderTimeout),
 		}
 
 		log.ContextLogger(ctx).Info("Proxy protocol is enabled")
diff --git a/internal/testhelper/testdata/testroot/config.yml b/internal/testhelper/testdata/testroot/config.yml
--- a/internal/testhelper/testdata/testroot/config.yml
+++ b/internal/testhelper/testdata/testroot/config.yml
@@ -1,3 +1,4 @@
 sshd:
   grace_period: 10
   client_alive_interval: 1m
+  proxy_header_timeout: 500ms
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1652972660 0
#      Thu May 19 15:04:20 2022 +0000
# Node ID 615b574a746d29556dfca756724aa990c7a3cc50
# Parent  b5a0002e1a3887d92469d212c04d461bda32c6ef
# Parent  615e6c9ba39d727a4f4941c36a00aa83011bacd6
Merge branch 'id-fix-proxy-header-timeout' into 'main'

Make ProxyHeaderTimeout configurable

See merge request gitlab-org/gitlab-shell!635

diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -99,11 +99,12 @@
 		sig := <-done
 		signal.Reset(syscall.SIGINT, syscall.SIGTERM)
 
-		log.WithContextFields(ctx, log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
+		gracePeriod := time.Duration(cfg.Server.GracePeriod)
+		log.WithContextFields(ctx, log.Fields{"shutdown_timeout_s": gracePeriod.Seconds(), "signal": sig.String()}).Info("Shutdown initiated")
 
 		server.Shutdown()
 
-		<-time.After(cfg.Server.GracePeriod())
+		<-time.After(gracePeriod)
 
 		cancel()
 
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -80,6 +80,8 @@
   client_alive_interval: 15
   # The server waits for this time (in seconds) for the ongoing connections to complete before shutting down. Defaults to 10.
   grace_period: 10
+  # A short timeout to decide to abort the connection if the protocol header is not seen within it. Defaults to 500ms
+  proxy_header_timeout: 500ms
   # The endpoint that returns 200 OK if the server is ready to receive incoming connections; otherwise, it returns 503 Service Unavailable. Defaults to "/start".
   readiness_probe: "/start"
   # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -21,20 +21,23 @@
 	defaultSecretFileName = ".gitlab_shell_secret"
 )
 
+type yamlDuration time.Duration
+
 type ServerConfig struct {
-	Listen                     string   `yaml:"listen,omitempty"`
-	ProxyProtocol              bool     `yaml:"proxy_protocol,omitempty"`
-	ProxyPolicy                string   `yaml:"proxy_policy,omitempty"`
-	WebListen                  string   `yaml:"web_listen,omitempty"`
-	ConcurrentSessionsLimit    int64    `yaml:"concurrent_sessions_limit,omitempty"`
-	ClientAliveIntervalSeconds int64    `yaml:"client_alive_interval,omitempty"`
-	GracePeriodSeconds         uint64   `yaml:"grace_period"`
-	ReadinessProbe             string   `yaml:"readiness_probe"`
-	LivenessProbe              string   `yaml:"liveness_probe"`
-	HostKeyFiles               []string `yaml:"host_key_files,omitempty"`
-	MACs                       []string `yaml:"macs"`
-	KexAlgorithms              []string `yaml:"kex_algorithms"`
-	Ciphers                    []string `yaml:"ciphers"`
+	Listen                  string       `yaml:"listen,omitempty"`
+	ProxyProtocol           bool         `yaml:"proxy_protocol,omitempty"`
+	ProxyPolicy             string       `yaml:"proxy_policy,omitempty"`
+	WebListen               string       `yaml:"web_listen,omitempty"`
+	ConcurrentSessionsLimit int64        `yaml:"concurrent_sessions_limit,omitempty"`
+	ClientAliveInterval     yamlDuration `yaml:"client_alive_interval,omitempty"`
+	GracePeriod             yamlDuration `yaml:"grace_period"`
+	ProxyHeaderTimeout      yamlDuration `yaml:"proxy_header_timeout"`
+	ReadinessProbe          string       `yaml:"readiness_probe"`
+	LivenessProbe           string       `yaml:"liveness_probe"`
+	HostKeyFiles            []string     `yaml:"host_key_files,omitempty"`
+	MACs                    []string     `yaml:"macs"`
+	KexAlgorithms           []string     `yaml:"kex_algorithms"`
+	Ciphers                 []string     `yaml:"ciphers"`
 }
 
 type HttpSettingsConfig struct {
@@ -79,13 +82,14 @@
 	}
 
 	DefaultServerConfig = ServerConfig{
-		Listen:                     "[::]:22",
-		WebListen:                  "localhost:9122",
-		ConcurrentSessionsLimit:    10,
-		GracePeriodSeconds:         10,
-		ClientAliveIntervalSeconds: 15,
-		ReadinessProbe:             "/start",
-		LivenessProbe:              "/health",
+		Listen:                  "[::]:22",
+		WebListen:               "localhost:9122",
+		ConcurrentSessionsLimit: 10,
+		GracePeriod:             yamlDuration(10 * time.Second),
+		ClientAliveInterval:     yamlDuration(15 * time.Second),
+		ProxyHeaderTimeout:      yamlDuration(500 * time.Millisecond),
+		ReadinessProbe:          "/start",
+		LivenessProbe:           "/health",
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
@@ -94,12 +98,15 @@
 	}
 )
 
-func (sc *ServerConfig) ClientAliveInterval() time.Duration {
-	return time.Duration(sc.ClientAliveIntervalSeconds) * time.Second
-}
+func (d *yamlDuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	var intDuration int
+	if err := unmarshal(&intDuration); err != nil {
+		return unmarshal((*time.Duration)(d))
+	}
 
-func (sc *ServerConfig) GracePeriod() time.Duration {
-	return time.Duration(sc.GracePeriodSeconds) * time.Second
+	*d = yamlDuration(time.Duration(intDuration) * time.Second)
+
+	return nil
 }
 
 func (c *Config) ApplyGlobalState() {
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -3,9 +3,11 @@
 import (
 	"os"
 	"testing"
+	"time"
 
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/stretchr/testify/require"
+	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
@@ -58,3 +60,40 @@
 
 	require.Equal(t, expectedMetricNames, actualNames)
 }
+
+func TestNewFromDir(t *testing.T) {
+	testhelper.PrepareTestRootDir(t)
+
+	cfg, err := NewFromDir(testhelper.TestRoot)
+	require.NoError(t, err)
+
+	require.Equal(t, 10*time.Second, time.Duration(cfg.Server.GracePeriod))
+	require.Equal(t, 1*time.Minute, time.Duration(cfg.Server.ClientAliveInterval))
+	require.Equal(t, 500*time.Millisecond, time.Duration(cfg.Server.ProxyHeaderTimeout))
+}
+
+func TestYAMLDuration(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		data     string
+		duration time.Duration
+	}{
+		{"seconds assumed by default", "duration: 10", 10 * time.Second},
+		{"milliseconds are parsed", "duration: 500ms", 500 * time.Millisecond},
+		{"minutes are parsed", "duration: 1m", 1 * time.Minute},
+	}
+
+	type durationCfg struct {
+		Duration yamlDuration `yaml:"duration"`
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			var cfg durationCfg
+			err := yaml.Unmarshal([]byte(tc.data), &cfg)
+			require.NoError(t, err)
+
+			require.Equal(t, tc.duration, time.Duration(cfg.Duration))
+		})
+	}
+}
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -46,8 +46,8 @@
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
-	if c.cfg.Server.ClientAliveIntervalSeconds > 0 {
-		ticker := time.NewTicker(c.cfg.Server.ClientAliveInterval())
+	if c.cfg.Server.ClientAliveInterval > 0 {
+		ticker := time.NewTicker(time.Duration(c.cfg.Server.ClientAliveInterval))
 		defer ticker.Stop()
 		go c.sendKeepAliveMsg(ctx, ticker)
 	}
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -80,7 +80,7 @@
 }
 
 func setup(sessionsNum int64, newChannel *fakeNewChannel) (*connection, chan ssh.NewChannel) {
-	cfg := &config.Config{Server: config.ServerConfig{ConcurrentSessionsLimit: sessionsNum, ClientAliveIntervalSeconds: 1}}
+	cfg := &config.Config{Server: config.ServerConfig{ConcurrentSessionsLimit: sessionsNum}}
 	conn := newConnection(cfg, "127.0.0.1:50000", &ssh.ServerConn{&fakeConn{}, nil})
 
 	chans := make(chan ssh.NewChannel, 1)
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -26,7 +26,6 @@
 	StatusReady
 	StatusOnShutdown
 	StatusClosed
-	ProxyHeaderTimeout = 90 * time.Second
 )
 
 type Server struct {
@@ -97,7 +96,7 @@
 		sshListener = &proxyproto.Listener{
 			Listener:          sshListener,
 			Policy:            s.requirePolicy,
-			ReadHeaderTimeout: ProxyHeaderTimeout,
+			ReadHeaderTimeout: time.Duration(s.Config.Server.ProxyHeaderTimeout),
 		}
 
 		log.ContextLogger(ctx).Info("Proxy protocol is enabled")
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -265,7 +265,6 @@
 	cfg.User = user
 	cfg.Server.Listen = serverUrl
 	cfg.Server.ConcurrentSessionsLimit = 1
-	cfg.Server.ClientAliveIntervalSeconds = 15
 	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
 
 	s, err := NewServer(cfg)
diff --git a/internal/testhelper/testdata/testroot/config.yml b/internal/testhelper/testdata/testroot/config.yml
--- a/internal/testhelper/testdata/testroot/config.yml
+++ b/internal/testhelper/testdata/testroot/config.yml
@@ -0,0 +1,4 @@
+sshd:
+  grace_period: 10
+  client_alive_interval: 1m
+  proxy_header_timeout: 500ms
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1652984464 25200
#      Thu May 19 11:21:04 2022 -0700
# Node ID 49a5f84c0ebe727653990d66163f182d1b3468dc
# Parent  615b574a746d29556dfca756724aa990c7a3cc50
Release 14.5.0

- Make ProxyHeaderTimeout configurable !635

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.5.0
+
+- Make ProxyHeaderTimeout configurable !635
+
 v14.4.0
 
 - Allow configuring SSH server algorithms !633
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.4.0
+14.5.0
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1652984905 0
#      Thu May 19 18:28:25 2022 +0000
# Node ID c1e06d31b6ef53cea0f58afa656b99c22fd38743
# Parent  615b574a746d29556dfca756724aa990c7a3cc50
# Parent  49a5f84c0ebe727653990d66163f182d1b3468dc
Merge branch 'sh-release-14.5.0' into 'main'

Release 14.5.0

See merge request gitlab-org/gitlab-shell!636

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.5.0
+
+- Make ProxyHeaderTimeout configurable !635
+
 v14.4.0
 
 - Allow configuring SSH server algorithms !633
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.4.0
+14.5.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653065479 -14400
#      Fri May 20 20:51:19 2022 +0400
# Node ID 20969ab322f10de98b2225800ec8d3c9e244bd3c
# Parent  c1e06d31b6ef53cea0f58afa656b99c22fd38743
Narrow supported kex algorithms

We don't support diffie-hellman-group14-sha1 via OpenSSH currently
Let's avoid introducing it in gitlab-sshd because it's using
weak hashing algorithm

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -89,7 +89,7 @@
   # Specifies the available message authentication code algorithms that are used for protecting data integrity
   macs: [hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1]
   # Specifies the available Key Exchange algorithms
-  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1]
+  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256]
   # Specified the ciphers allowed
   ciphers: [aes128-gcm@openssh.com, chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-ctr, aes192-ctr,aes256-ctr]
   # SSH host key files.
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -16,13 +16,24 @@
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
-var supportedMACs = []string{
-	"hmac-sha2-256-etm@openssh.com",
-	"hmac-sha2-512-etm@openssh.com",
-	"hmac-sha2-256",
-	"hmac-sha2-512",
-	"hmac-sha1",
-}
+var (
+	supportedMACs = []string{
+		"hmac-sha2-256-etm@openssh.com",
+		"hmac-sha2-512-etm@openssh.com",
+		"hmac-sha2-256",
+		"hmac-sha2-512",
+		"hmac-sha1",
+	}
+
+	supportedKeyExchanges = []string{
+		"curve25519-sha256",
+		"curve25519-sha256@libssh.org",
+		"ecdh-sha2-nistp256",
+		"ecdh-sha2-nistp384",
+		"ecdh-sha2-nistp521",
+		"diffie-hellman-group14-sha256",
+	}
+)
 
 type serverConfig struct {
 	cfg                  *config.Config
@@ -102,6 +113,8 @@
 
 	if len(s.cfg.Server.KexAlgorithms) > 0 {
 		sshCfg.KeyExchanges = s.cfg.Server.KexAlgorithms
+	} else {
+		sshCfg.KeyExchanges = supportedKeyExchanges
 	}
 
 	if len(s.cfg.Server.Ciphers) > 0 {
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
--- a/internal/sshd/server_config_test.go
+++ b/internal/sshd/server_config_test.go
@@ -85,23 +85,13 @@
 	sshServerConfig := srvCfg.get(context.Background())
 
 	require.Equal(t, supportedMACs, sshServerConfig.MACs)
-	require.Nil(t, sshServerConfig.KeyExchanges)
+	require.Equal(t, supportedKeyExchanges, sshServerConfig.KeyExchanges)
 	require.Nil(t, sshServerConfig.Ciphers)
 
 	sshServerConfig.SetDefaults()
 
 	require.Equal(t, supportedMACs, sshServerConfig.MACs)
-
-	defaultKeyExchanges := []string{
-		"curve25519-sha256",
-		"curve25519-sha256@libssh.org",
-		"ecdh-sha2-nistp256",
-		"ecdh-sha2-nistp384",
-		"ecdh-sha2-nistp521",
-		"diffie-hellman-group14-sha256",
-		"diffie-hellman-group14-sha1",
-	}
-	require.Equal(t, defaultKeyExchanges, sshServerConfig.KeyExchanges)
+	require.Equal(t, supportedKeyExchanges, sshServerConfig.KeyExchanges)
 
 	defaultCiphers := []string{
 		"aes128-gcm@openssh.com",
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1653066024 0
#      Fri May 20 17:00:24 2022 +0000
# Node ID 8f6f9f16dc098a781d4b98a6b91b8a2c78037d50
# Parent  c1e06d31b6ef53cea0f58afa656b99c22fd38743
# Parent  20969ab322f10de98b2225800ec8d3c9e244bd3c
Merge branch 'id-set-supported-kex-algos' into 'main'

Narrow supported kex algorithms

See merge request gitlab-org/gitlab-shell!638

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -89,7 +89,7 @@
   # Specifies the available message authentication code algorithms that are used for protecting data integrity
   macs: [hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1]
   # Specifies the available Key Exchange algorithms
-  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1]
+  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256]
   # Specified the ciphers allowed
   ciphers: [aes128-gcm@openssh.com, chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-ctr, aes192-ctr,aes256-ctr]
   # SSH host key files.
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -16,13 +16,24 @@
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
-var supportedMACs = []string{
-	"hmac-sha2-256-etm@openssh.com",
-	"hmac-sha2-512-etm@openssh.com",
-	"hmac-sha2-256",
-	"hmac-sha2-512",
-	"hmac-sha1",
-}
+var (
+	supportedMACs = []string{
+		"hmac-sha2-256-etm@openssh.com",
+		"hmac-sha2-512-etm@openssh.com",
+		"hmac-sha2-256",
+		"hmac-sha2-512",
+		"hmac-sha1",
+	}
+
+	supportedKeyExchanges = []string{
+		"curve25519-sha256",
+		"curve25519-sha256@libssh.org",
+		"ecdh-sha2-nistp256",
+		"ecdh-sha2-nistp384",
+		"ecdh-sha2-nistp521",
+		"diffie-hellman-group14-sha256",
+	}
+)
 
 type serverConfig struct {
 	cfg                  *config.Config
@@ -102,6 +113,8 @@
 
 	if len(s.cfg.Server.KexAlgorithms) > 0 {
 		sshCfg.KeyExchanges = s.cfg.Server.KexAlgorithms
+	} else {
+		sshCfg.KeyExchanges = supportedKeyExchanges
 	}
 
 	if len(s.cfg.Server.Ciphers) > 0 {
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
--- a/internal/sshd/server_config_test.go
+++ b/internal/sshd/server_config_test.go
@@ -85,23 +85,13 @@
 	sshServerConfig := srvCfg.get(context.Background())
 
 	require.Equal(t, supportedMACs, sshServerConfig.MACs)
-	require.Nil(t, sshServerConfig.KeyExchanges)
+	require.Equal(t, supportedKeyExchanges, sshServerConfig.KeyExchanges)
 	require.Nil(t, sshServerConfig.Ciphers)
 
 	sshServerConfig.SetDefaults()
 
 	require.Equal(t, supportedMACs, sshServerConfig.MACs)
-
-	defaultKeyExchanges := []string{
-		"curve25519-sha256",
-		"curve25519-sha256@libssh.org",
-		"ecdh-sha2-nistp256",
-		"ecdh-sha2-nistp384",
-		"ecdh-sha2-nistp521",
-		"diffie-hellman-group14-sha256",
-		"diffie-hellman-group14-sha1",
-	}
-	require.Equal(t, defaultKeyExchanges, sshServerConfig.KeyExchanges)
+	require.Equal(t, supportedKeyExchanges, sshServerConfig.KeyExchanges)
 
 	defaultCiphers := []string{
 		"aes128-gcm@openssh.com",
# HG changeset patch
# User Hendrik Meyer <t4cc0re@gitlab.com>
# Date 1653104624 0
#      Sat May 21 03:43:44 2022 +0000
# Node ID 3f5e048b2ab5bb122761a7d63d834e0a60880355
# Parent  8f6f9f16dc098a781d4b98a6b91b8a2c78037d50
Introduce a GitLab-SSHD server version during handshake

diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -103,6 +103,7 @@
 				},
 			}, nil
 		},
+		ServerVersion: "SSH-2.0-GitLab-SSHD",
 	}
 
 	if len(s.cfg.Server.MACs) > 0 {
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653104624 0
#      Sat May 21 03:43:44 2022 +0000
# Node ID 236ca48fc6332d2da46c34134fc09d773d352196
# Parent  8f6f9f16dc098a781d4b98a6b91b8a2c78037d50
# Parent  3f5e048b2ab5bb122761a7d63d834e0a60880355
Merge branch 'T4cC0re-main-patch-11870' into 'main'

Introduce a GitLab-SSHD server version during handshake

See merge request gitlab-org/gitlab-shell!640

diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -103,6 +103,7 @@
 				},
 			}, nil
 		},
+		ServerVersion: "SSH-2.0-GitLab-SSHD",
 	}
 
 	if len(s.cfg.Server.MACs) > 0 {
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1653085917 25200
#      Fri May 20 15:31:57 2022 -0700
# Node ID e6c3ba396f7cf96434e60197f1443e62fe8cb80f
# Parent  8f6f9f16dc098a781d4b98a6b91b8a2c78037d50
Downgrade host key mismatch messages from warning to debug

In production, we often see SSH key scans requesting host key
algorithms that we don't support, such as `sk-ssh-ed25519@openssh.com`
or `sk-ecdsa-sha2-nistp256@openssh.com`.

These messages might be useful if someone forgets to configure a host
key that should be supported, but most of the time they are noise.

This commit downgrades these messages to DEBUG.

Relates to https://gitlab.com/gitlab-org/gitlab-shell/-/issues/581

Changelog: changed

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -11,6 +11,7 @@
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.2
 	github.com/prometheus/client_golang v1.12.1
+	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
 	gitlab.com/gitlab-org/labkit v1.14.0
@@ -59,7 +60,6 @@
 	github.com/prometheus/procfs v0.7.3 // indirect
 	github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect
 	github.com/shirou/gopsutil/v3 v3.21.2 // indirect
-	github.com/sirupsen/logrus v1.8.1 // indirect
 	github.com/tinylib/msgp v1.1.2 // indirect
 	github.com/tklauser/go-sysconf v0.3.4 // indirect
 	github.com/tklauser/numcpus v0.2.1 // indirect
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -10,6 +10,7 @@
 	"time"
 
 	"github.com/pires/go-proxyproto"
+	"github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -38,6 +39,18 @@
 	serverConfig *serverConfig
 }
 
+func logSSHInitError(ctxlog *logrus.Entry, err error) {
+	msg := "server: handleConn: failed to initialize SSH connection"
+
+	logger := ctxlog.WithError(err)
+
+	if strings.Contains(err.Error(), "no common algorithm for host key") {
+		logger.Debug(msg)
+	} else {
+		logger.Warn(msg)
+	}
+}
+
 func NewServer(cfg *config.Config) (*Server, error) {
 	serverConfig, err := newServerConfig(cfg)
 	if err != nil {
@@ -172,7 +185,7 @@
 
 	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
-		ctxlog.WithError(err).Warn("server: handleConn: failed to initialize SSH connection")
+		logSSHInitError(ctxlog, err)
 		return
 	}
 	go ssh.DiscardRequests(reqs)
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1653086772 25200
#      Fri May 20 15:46:12 2022 -0700
# Node ID f6c346f0e4fcc9e178042f0a02cc349339dd9eba
# Parent  e6c3ba396f7cf96434e60197f1443e62fe8cb80f
Downgrade handleConn start message to debug

This message doesn't provide that much value, so let's just drop it.

diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -181,7 +181,7 @@
 		}
 	}()
 
-	ctxlog.Info("server: handleConn: start")
+	ctxlog.Debug("server: handleConn: start")
 
 	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653109080 0
#      Sat May 21 04:58:00 2022 +0000
# Node ID f32f24a0e5383a503775155b9fc08d7dc3b8b887
# Parent  236ca48fc6332d2da46c34134fc09d773d352196
# Parent  f6c346f0e4fcc9e178042f0a02cc349339dd9eba
Merge branch 'sh-downgrade-host-key-errors' into 'main'

Downgrade host key mismatch messages from warning to debug

See merge request gitlab-org/gitlab-shell!639

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -11,6 +11,7 @@
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.2
 	github.com/prometheus/client_golang v1.12.1
+	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
 	gitlab.com/gitlab-org/labkit v1.14.0
@@ -59,7 +60,6 @@
 	github.com/prometheus/procfs v0.7.3 // indirect
 	github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect
 	github.com/shirou/gopsutil/v3 v3.21.2 // indirect
-	github.com/sirupsen/logrus v1.8.1 // indirect
 	github.com/tinylib/msgp v1.1.2 // indirect
 	github.com/tklauser/go-sysconf v0.3.4 // indirect
 	github.com/tklauser/numcpus v0.2.1 // indirect
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -10,6 +10,7 @@
 	"time"
 
 	"github.com/pires/go-proxyproto"
+	"github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -38,6 +39,18 @@
 	serverConfig *serverConfig
 }
 
+func logSSHInitError(ctxlog *logrus.Entry, err error) {
+	msg := "server: handleConn: failed to initialize SSH connection"
+
+	logger := ctxlog.WithError(err)
+
+	if strings.Contains(err.Error(), "no common algorithm for host key") {
+		logger.Debug(msg)
+	} else {
+		logger.Warn(msg)
+	}
+}
+
 func NewServer(cfg *config.Config) (*Server, error) {
 	serverConfig, err := newServerConfig(cfg)
 	if err != nil {
@@ -168,11 +181,11 @@
 		}
 	}()
 
-	ctxlog.Info("server: handleConn: start")
+	ctxlog.Debug("server: handleConn: start")
 
 	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
-		ctxlog.WithError(err).Warn("server: handleConn: failed to initialize SSH connection")
+		logSSHInitError(ctxlog, err)
 		return
 	}
 	go ssh.DiscardRequests(reqs)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653108915 -14400
#      Sat May 21 08:55:15 2022 +0400
# Node ID 3b1e131ec2437c044518d368bda770f2f0185e0f
# Parent  f32f24a0e5383a503775155b9fc08d7dc3b8b887
Display constistently in gitlab-sshd and gitlab-shell

- Use console package to format the errors in gitlab-sshd
- Suppress internal Gitaly errors in client output

diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -58,13 +58,11 @@
 	exitStatus, err := handler(childCtx, conn)
 
 	if err != nil {
-		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
-			ctxlog.WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Gitaly is unavailable")
+		ctxlog.WithError(err).WithFields(log.Fields{"exit_status": exitStatus}).Error("Failed to execute Git command")
 
-			return fmt.Errorf("The git server, Gitaly, is not available at this time. Please contact your administrator.")
+		if grpcstatus.Code(err) == grpccodes.Unavailable {
+			return grpcstatus.Error(grpccodes.Unavailable, "The git server, Gitaly, is not available at this time. Please contact your administrator.")
 		}
-
-		ctxlog.WithError(err).WithFields(log.Fields{"exit_status": exitStatus}).Error("Failed to execute Git command")
 	}
 
 	return err
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -84,10 +84,8 @@
 		},
 	)
 
-	expectedErr := grpcstatus.Error(grpccodes.Unavailable, "error")
-	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, expectedErr))
-
-	require.EqualError(t, err, "The git server, Gitaly, is not available at this time. Please contact your administrator.")
+	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, grpcstatus.Error(grpccodes.Unavailable, "error")))
+	require.Equal(t, err, grpcstatus.Error(grpccodes.Unavailable, "The git server, Gitaly, is not available at this time. Please contact your administrator."))
 }
 
 func TestRunGitalyCommandMetadata(t *testing.T) {
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -8,11 +8,14 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 	"golang.org/x/crypto/ssh"
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
@@ -158,10 +161,12 @@
 
 	cmd, err := shellCmd.NewWithKey(s.gitlabKeyId, env, s.cfg, rw)
 	if err != nil {
-		if !errors.Is(err, disallowedcommand.Error) {
-			s.toStderr(ctx, "Failed to parse command: %v\n", err.Error())
+		if errors.Is(err, disallowedcommand.Error) {
+			s.toStderr(ctx, "ERROR: Unknown command: %v\n", s.execCmd)
+		} else {
+			s.toStderr(ctx, "ERROR: Failed to parse command: %v\n", err.Error())
 		}
-		s.toStderr(ctx, "Unknown command: %v\n", s.execCmd)
+
 		return 128, err
 	}
 
@@ -169,7 +174,11 @@
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("session: handleShell: executing command")
 
 	if err := cmd.Execute(ctx); err != nil {
-		s.toStderr(ctx, "remote: ERROR: %v\n", err.Error())
+		grpcStatus := grpcstatus.Convert(err)
+		if grpcStatus.Code() != grpccodes.Internal {
+			s.toStderr(ctx, "ERROR: %v\n", grpcStatus.Message())
+		}
+
 		return 1, err
 	}
 
@@ -181,7 +190,7 @@
 func (s *session) toStderr(ctx context.Context, format string, args ...interface{}) {
 	out := fmt.Sprintf(format, args...)
 	log.WithContextFields(ctx, log.Fields{"stderr": out}).Debug("session: toStderr: output")
-	fmt.Fprint(s.channel.Stderr(), out)
+	console.DisplayWarningMessage(out, s.channel.Stderr())
 }
 
 func (s *session) exit(ctx context.Context, status uint32) {
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -13,6 +13,7 @@
 
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
 )
 
 type fakeChannel struct {
@@ -160,14 +161,14 @@
 		{
 			desc:              "fails to parse command",
 			cmd:               `\`,
-			errMsg:            "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
+			errMsg:            "ERROR: Failed to parse command: Invalid SSH command: invalid command line string\n",
 			gitlabKeyId:       "root",
 			expectedErrString: "Invalid SSH command: invalid command line string",
 			expectedExitCode:  128,
 		}, {
 			desc:              "specified command is unknown",
 			cmd:               "unknown-command",
-			errMsg:            "Unknown command: unknown-command\n",
+			errMsg:            "ERROR: Unknown command: unknown-command\n",
 			gitlabKeyId:       "root",
 			expectedErrString: "Disallowed command",
 			expectedExitCode:  128,
@@ -175,7 +176,7 @@
 			desc:              "fails to parse command",
 			cmd:               "discover",
 			gitlabKeyId:       "",
-			errMsg:            "remote: ERROR: Failed to get username: who='' is invalid\n",
+			errMsg:            "ERROR: Failed to get username: who='' is invalid\n",
 			expectedErrString: "Failed to get username: who='' is invalid",
 			expectedExitCode:  1,
 		}, {
@@ -208,7 +209,14 @@
 			}
 
 			require.Equal(t, tc.expectedExitCode, exitCode)
-			require.Equal(t, tc.errMsg, out.String())
+
+			formattedErr := &bytes.Buffer{}
+			if tc.errMsg != "" {
+				console.DisplayWarningMessage(tc.errMsg, formattedErr)
+				require.Equal(t, formattedErr.String(), out.String())
+			} else {
+				require.Equal(t, tc.errMsg, out.String())
+			}
 		})
 	}
 }
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653071530 -14400
#      Fri May 20 22:32:10 2022 +0400
# Node ID 1d9befdbb63d366fa604b030d2489075503c9517
# Parent  3b1e131ec2437c044518d368bda770f2f0185e0f
Exclude Gitaly unavailable error from error rate

When a user hits repository rate limit, Gitaly returns an error
that the request can't be handled (Gitaly unavailable)

We should avoid this error to avoid exceeding the error rate

diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -41,7 +41,7 @@
 	require.NoError(t, err)
 
 	var actualNames []string
-	for _, m := range ms[0:10] {
+	for _, m := range ms[0:9] {
 		actualNames = append(actualNames, m.GetName())
 	}
 
@@ -49,7 +49,6 @@
 		"gitlab_shell_http_in_flight_requests",
 		"gitlab_shell_http_request_duration_seconds",
 		"gitlab_shell_http_requests_total",
-		"gitlab_shell_sshd_canceled_sessions",
 		"gitlab_shell_sshd_concurrent_limited_sessions_total",
 		"gitlab_shell_sshd_in_flight_connections",
 		"gitlab_shell_sshd_session_duration_seconds",
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -77,15 +77,6 @@
 		},
 	)
 
-	SshdCanceledSessions = promauto.NewCounter(
-		prometheus.CounterOpts{
-			Namespace: namespace,
-			Subsystem: sshdSubsystem,
-			Name:      sshdCanceledSessionsName,
-			Help:      "The number of canceled gitlab-sshd sessions.",
-		},
-	)
-
 	SliSshdSessionsTotal = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Name: sliSshdSessionsTotalName,
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -89,14 +89,7 @@
 			metrics.SliSshdSessionsTotal.Inc()
 			err := handler(ctx, channel, requests)
 			if err != nil {
-				if grpcstatus.Convert(err).Code() == grpccodes.Canceled {
-					metrics.SshdCanceledSessions.Inc()
-				} else {
-					var apiError *client.ApiError
-					if !errors.As(err, &apiError) {
-						metrics.SliSshdSessionsErrorsTotal.Inc()
-					}
-				}
+				c.trackError(err)
 			}
 
 			ctxlog.Info("connection: handle: done")
@@ -126,3 +119,17 @@
 		}
 	}
 }
+
+func (c *connection) trackError(err error) {
+	var apiError *client.ApiError
+	if errors.As(err, &apiError) {
+		return
+	}
+
+	grpcCode := grpcstatus.Code(err)
+	if grpcCode == grpccodes.Canceled || grpcCode == grpccodes.Unavailable {
+		return
+	}
+
+	metrics.SliSshdSessionsErrorsTotal.Inc()
+}
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -200,7 +200,6 @@
 	// https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#pkg-index
 	initialSessionsTotal := testutil.ToFloat64(metrics.SliSshdSessionsTotal)
 	initialSessionsErrorTotal := testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal)
-	initialCanceledSessions := testutil.ToFloat64(metrics.SshdCanceledSessions)
 
 	newChannel := &fakeNewChannel{channelType: "session"}
 
@@ -212,17 +211,15 @@
 
 	require.InDelta(t, initialSessionsTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
-	require.InDelta(t, initialCanceledSessions, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
 
 	conn, chans = setup(1, newChannel)
 	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
-		return grpcstatus.Error(grpccodes.Canceled, "error")
+		return grpcstatus.Error(grpccodes.Canceled, "canceled")
 	})
 
 	require.InDelta(t, initialSessionsTotal+2, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
-	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
 
 	conn, chans = setup(1, newChannel)
 	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
@@ -232,5 +229,13 @@
 
 	require.InDelta(t, initialSessionsTotal+3, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
-	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
+
+	conn, chans = setup(1, newChannel)
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		close(chans)
+		return grpcstatus.Error(grpccodes.Unavailable, "unavailable")
+	})
+
+	require.InDelta(t, initialSessionsTotal+4, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 }
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653109441 -14400
#      Sat May 21 09:04:01 2022 +0400
# Node ID 0ef9f95f2356e122bac2b5c352ab26c302ae8a1e
# Parent  1d9befdbb63d366fa604b030d2489075503c9517
Downgrade auth EOF messages from warning to debug

The errors happen when a client closes a connection on handshake
They can be ignored to avoid noise

diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -44,7 +44,7 @@
 
 	logger := ctxlog.WithError(err)
 
-	if strings.Contains(err.Error(), "no common algorithm for host key") {
+	if strings.Contains(err.Error(), "no common algorithm for host key") || err.Error() == "EOF" {
 		logger.Debug(msg)
 	} else {
 		logger.Warn(msg)
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1653114095 0
#      Sat May 21 06:21:35 2022 +0000
# Node ID 6547a3124efd00569abddf812d3ad9716d74843a
# Parent  f32f24a0e5383a503775155b9fc08d7dc3b8b887
# Parent  0ef9f95f2356e122bac2b5c352ab26c302ae8a1e
Merge branch 'id-ignore-gitaly-unavailable-errors' into 'main'

Exclude Gitaly unavailable error from error rate

See merge request gitlab-org/gitlab-shell!641

diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -41,7 +41,7 @@
 	require.NoError(t, err)
 
 	var actualNames []string
-	for _, m := range ms[0:10] {
+	for _, m := range ms[0:9] {
 		actualNames = append(actualNames, m.GetName())
 	}
 
@@ -49,7 +49,6 @@
 		"gitlab_shell_http_in_flight_requests",
 		"gitlab_shell_http_request_duration_seconds",
 		"gitlab_shell_http_requests_total",
-		"gitlab_shell_sshd_canceled_sessions",
 		"gitlab_shell_sshd_concurrent_limited_sessions_total",
 		"gitlab_shell_sshd_in_flight_connections",
 		"gitlab_shell_sshd_session_duration_seconds",
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -58,13 +58,11 @@
 	exitStatus, err := handler(childCtx, conn)
 
 	if err != nil {
-		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
-			ctxlog.WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Gitaly is unavailable")
+		ctxlog.WithError(err).WithFields(log.Fields{"exit_status": exitStatus}).Error("Failed to execute Git command")
 
-			return fmt.Errorf("The git server, Gitaly, is not available at this time. Please contact your administrator.")
+		if grpcstatus.Code(err) == grpccodes.Unavailable {
+			return grpcstatus.Error(grpccodes.Unavailable, "The git server, Gitaly, is not available at this time. Please contact your administrator.")
 		}
-
-		ctxlog.WithError(err).WithFields(log.Fields{"exit_status": exitStatus}).Error("Failed to execute Git command")
 	}
 
 	return err
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -84,10 +84,8 @@
 		},
 	)
 
-	expectedErr := grpcstatus.Error(grpccodes.Unavailable, "error")
-	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, expectedErr))
-
-	require.EqualError(t, err, "The git server, Gitaly, is not available at this time. Please contact your administrator.")
+	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, grpcstatus.Error(grpccodes.Unavailable, "error")))
+	require.Equal(t, err, grpcstatus.Error(grpccodes.Unavailable, "The git server, Gitaly, is not available at this time. Please contact your administrator."))
 }
 
 func TestRunGitalyCommandMetadata(t *testing.T) {
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -77,15 +77,6 @@
 		},
 	)
 
-	SshdCanceledSessions = promauto.NewCounter(
-		prometheus.CounterOpts{
-			Namespace: namespace,
-			Subsystem: sshdSubsystem,
-			Name:      sshdCanceledSessionsName,
-			Help:      "The number of canceled gitlab-sshd sessions.",
-		},
-	)
-
 	SliSshdSessionsTotal = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Name: sliSshdSessionsTotalName,
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -89,14 +89,7 @@
 			metrics.SliSshdSessionsTotal.Inc()
 			err := handler(ctx, channel, requests)
 			if err != nil {
-				if grpcstatus.Convert(err).Code() == grpccodes.Canceled {
-					metrics.SshdCanceledSessions.Inc()
-				} else {
-					var apiError *client.ApiError
-					if !errors.As(err, &apiError) {
-						metrics.SliSshdSessionsErrorsTotal.Inc()
-					}
-				}
+				c.trackError(err)
 			}
 
 			ctxlog.Info("connection: handle: done")
@@ -126,3 +119,17 @@
 		}
 	}
 }
+
+func (c *connection) trackError(err error) {
+	var apiError *client.ApiError
+	if errors.As(err, &apiError) {
+		return
+	}
+
+	grpcCode := grpcstatus.Code(err)
+	if grpcCode == grpccodes.Canceled || grpcCode == grpccodes.Unavailable {
+		return
+	}
+
+	metrics.SliSshdSessionsErrorsTotal.Inc()
+}
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -200,7 +200,6 @@
 	// https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#pkg-index
 	initialSessionsTotal := testutil.ToFloat64(metrics.SliSshdSessionsTotal)
 	initialSessionsErrorTotal := testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal)
-	initialCanceledSessions := testutil.ToFloat64(metrics.SshdCanceledSessions)
 
 	newChannel := &fakeNewChannel{channelType: "session"}
 
@@ -212,17 +211,15 @@
 
 	require.InDelta(t, initialSessionsTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
-	require.InDelta(t, initialCanceledSessions, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
 
 	conn, chans = setup(1, newChannel)
 	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
-		return grpcstatus.Error(grpccodes.Canceled, "error")
+		return grpcstatus.Error(grpccodes.Canceled, "canceled")
 	})
 
 	require.InDelta(t, initialSessionsTotal+2, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
-	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
 
 	conn, chans = setup(1, newChannel)
 	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
@@ -232,5 +229,13 @@
 
 	require.InDelta(t, initialSessionsTotal+3, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
-	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
+
+	conn, chans = setup(1, newChannel)
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		close(chans)
+		return grpcstatus.Error(grpccodes.Unavailable, "unavailable")
+	})
+
+	require.InDelta(t, initialSessionsTotal+4, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 }
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -8,11 +8,14 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 	"golang.org/x/crypto/ssh"
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
@@ -158,10 +161,12 @@
 
 	cmd, err := shellCmd.NewWithKey(s.gitlabKeyId, env, s.cfg, rw)
 	if err != nil {
-		if !errors.Is(err, disallowedcommand.Error) {
-			s.toStderr(ctx, "Failed to parse command: %v\n", err.Error())
+		if errors.Is(err, disallowedcommand.Error) {
+			s.toStderr(ctx, "ERROR: Unknown command: %v\n", s.execCmd)
+		} else {
+			s.toStderr(ctx, "ERROR: Failed to parse command: %v\n", err.Error())
 		}
-		s.toStderr(ctx, "Unknown command: %v\n", s.execCmd)
+
 		return 128, err
 	}
 
@@ -169,7 +174,11 @@
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("session: handleShell: executing command")
 
 	if err := cmd.Execute(ctx); err != nil {
-		s.toStderr(ctx, "remote: ERROR: %v\n", err.Error())
+		grpcStatus := grpcstatus.Convert(err)
+		if grpcStatus.Code() != grpccodes.Internal {
+			s.toStderr(ctx, "ERROR: %v\n", grpcStatus.Message())
+		}
+
 		return 1, err
 	}
 
@@ -181,7 +190,7 @@
 func (s *session) toStderr(ctx context.Context, format string, args ...interface{}) {
 	out := fmt.Sprintf(format, args...)
 	log.WithContextFields(ctx, log.Fields{"stderr": out}).Debug("session: toStderr: output")
-	fmt.Fprint(s.channel.Stderr(), out)
+	console.DisplayWarningMessage(out, s.channel.Stderr())
 }
 
 func (s *session) exit(ctx context.Context, status uint32) {
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -13,6 +13,7 @@
 
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
 )
 
 type fakeChannel struct {
@@ -160,14 +161,14 @@
 		{
 			desc:              "fails to parse command",
 			cmd:               `\`,
-			errMsg:            "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
+			errMsg:            "ERROR: Failed to parse command: Invalid SSH command: invalid command line string\n",
 			gitlabKeyId:       "root",
 			expectedErrString: "Invalid SSH command: invalid command line string",
 			expectedExitCode:  128,
 		}, {
 			desc:              "specified command is unknown",
 			cmd:               "unknown-command",
-			errMsg:            "Unknown command: unknown-command\n",
+			errMsg:            "ERROR: Unknown command: unknown-command\n",
 			gitlabKeyId:       "root",
 			expectedErrString: "Disallowed command",
 			expectedExitCode:  128,
@@ -175,7 +176,7 @@
 			desc:              "fails to parse command",
 			cmd:               "discover",
 			gitlabKeyId:       "",
-			errMsg:            "remote: ERROR: Failed to get username: who='' is invalid\n",
+			errMsg:            "ERROR: Failed to get username: who='' is invalid\n",
 			expectedErrString: "Failed to get username: who='' is invalid",
 			expectedExitCode:  1,
 		}, {
@@ -208,7 +209,14 @@
 			}
 
 			require.Equal(t, tc.expectedExitCode, exitCode)
-			require.Equal(t, tc.errMsg, out.String())
+
+			formattedErr := &bytes.Buffer{}
+			if tc.errMsg != "" {
+				console.DisplayWarningMessage(tc.errMsg, formattedErr)
+				require.Equal(t, formattedErr.String(), out.String())
+			} else {
+				require.Equal(t, tc.errMsg, out.String())
+			}
 		})
 	}
 }
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -44,7 +44,7 @@
 
 	logger := ctxlog.WithError(err)
 
-	if strings.Contains(err.Error(), "no common algorithm for host key") {
+	if strings.Contains(err.Error(), "no common algorithm for host key") || err.Error() == "EOF" {
 		logger.Debug(msg)
 	} else {
 		logger.Warn(msg)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653114539 -14400
#      Sat May 21 10:28:59 2022 +0400
# Node ID 1874de47d59cb1ac4bb37508a1a61ff98bd67177
# Parent  6547a3124efd00569abddf812d3ad9716d74843a
Release 14.6.0

- Exclude Gitaly unavailable error from error rate !641
- Downgrade auth EOF messages from warning to debug !641
- Display constistently in gitlab-sshd and gitlab-shell !641
- Downgrade host key mismatch messages from warning to debug !639
- Introduce a GitLab-SSHD server version during handshake !640
- Narrow supported kex algorithms !638

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,12 @@
+v14.6.0
+
+- Exclude Gitaly unavailable error from error rate !641
+- Downgrade auth EOF messages from warning to debug !641
+- Display constistently in gitlab-sshd and gitlab-shell !641
+- Downgrade host key mismatch messages from warning to debug !639
+- Introduce a GitLab-SSHD server version during handshake !640
+- Narrow supported kex algorithms !638
+
 v14.5.0
 
 - Make ProxyHeaderTimeout configurable !635
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.5.0
+14.6.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653115022 0
#      Sat May 21 06:37:02 2022 +0000
# Node ID 363bcf081e7da20b57c13161133c718c3aa63c4c
# Parent  6547a3124efd00569abddf812d3ad9716d74843a
# Parent  1874de47d59cb1ac4bb37508a1a61ff98bd67177
Merge branch 'id-release-14-6-0' into 'main'

Release 14.6.0

See merge request gitlab-org/gitlab-shell!643

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,12 @@
+v14.6.0
+
+- Exclude Gitaly unavailable error from error rate !641
+- Downgrade auth EOF messages from warning to debug !641
+- Display constistently in gitlab-sshd and gitlab-shell !641
+- Downgrade host key mismatch messages from warning to debug !639
+- Introduce a GitLab-SSHD server version during handshake !640
+- Narrow supported kex algorithms !638
+
 v14.5.0
 
 - Make ProxyHeaderTimeout configurable !635
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.5.0
+14.6.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653294631 -14400
#      Mon May 23 12:30:31 2022 +0400
# Node ID 0c4f65831e4a5e0456be9da50e55ae4b3069526a
# Parent  363bcf081e7da20b57c13161133c718c3aa63c4c
Return support for diffie-hellman-group14-sha1

It seems that a lot of users rely on this, let's return it and
deprecated later to make the migration less disruptive

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -89,7 +89,7 @@
   # Specifies the available message authentication code algorithms that are used for protecting data integrity
   macs: [hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1]
   # Specifies the available Key Exchange algorithms
-  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256]
+  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1]
   # Specified the ciphers allowed
   ciphers: [aes128-gcm@openssh.com, chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-ctr, aes192-ctr,aes256-ctr]
   # SSH host key files.
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -32,6 +32,7 @@
 		"ecdh-sha2-nistp384",
 		"ecdh-sha2-nistp521",
 		"diffie-hellman-group14-sha256",
+		"diffie-hellman-group14-sha1",
 	}
 )
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653295311 0
#      Mon May 23 08:41:51 2022 +0000
# Node ID 1187698232fe6bb93f202d369f8e9c80d76e0d7f
# Parent  363bcf081e7da20b57c13161133c718c3aa63c4c
# Parent  0c4f65831e4a5e0456be9da50e55ae4b3069526a
Merge branch 'id-revert-narrowing-kex-algos' into 'main'

Return support for diffie-hellman-group14-sha1

See merge request gitlab-org/gitlab-shell!644

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -89,7 +89,7 @@
   # Specifies the available message authentication code algorithms that are used for protecting data integrity
   macs: [hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1]
   # Specifies the available Key Exchange algorithms
-  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256]
+  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1]
   # Specified the ciphers allowed
   ciphers: [aes128-gcm@openssh.com, chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-ctr, aes192-ctr,aes256-ctr]
   # SSH host key files.
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -32,6 +32,7 @@
 		"ecdh-sha2-nistp384",
 		"ecdh-sha2-nistp521",
 		"diffie-hellman-group14-sha256",
+		"diffie-hellman-group14-sha1",
 	}
 )
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653295351 -14400
#      Mon May 23 12:42:31 2022 +0400
# Node ID 47b689bfce2d6842187e2ceb197185082af8a8d5
# Parent  1187698232fe6bb93f202d369f8e9c80d76e0d7f
Release v14.6.1

- Return support for diffie-hellman-group14-sha1 !644

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.6.1
+
+- Return support for diffie-hellman-group14-sha1 !644
+
 v14.6.0
 
 - Exclude Gitaly unavailable error from error rate !641
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.6.0
+14.6.1
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653295769 0
#      Mon May 23 08:49:29 2022 +0000
# Node ID 7388be2b682515483bb91b68a351c5b0d2590c17
# Parent  1187698232fe6bb93f202d369f8e9c80d76e0d7f
# Parent  47b689bfce2d6842187e2ceb197185082af8a8d5
Merge branch 'id-release-14-6-1' into 'main'

Release v14.6.1

See merge request gitlab-org/gitlab-shell!645

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.6.1
+
+- Return support for diffie-hellman-group14-sha1 !644
+
 v14.6.0
 
 - Exclude Gitaly unavailable error from error rate !641
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.6.0
+14.6.1
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653310430 -14400
#      Mon May 23 16:53:50 2022 +0400
# Node ID 929d42f39088c63cb672deec90e29e8266ae64cb
# Parent  7388be2b682515483bb91b68a351c5b0d2590c17
Move connection init into connection.go

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -3,6 +3,8 @@
 import (
 	"context"
 	"errors"
+	"net"
+	"strings"
 	"time"
 
 	"golang.org/x/crypto/ssh"
@@ -22,52 +24,91 @@
 var EOFTimeout = 10 * time.Second
 
 type connection struct {
-	cfg                *config.Config
-	concurrentSessions *semaphore.Weighted
-	remoteAddr         string
-	sconn              *ssh.ServerConn
-	maxSessions        int64
+	cfg                      *config.Config
+	concurrentSessions       *semaphore.Weighted
+	nconn                    net.Conn
+	maxSessions              int64
+	remoteAddr               string
+	started                  time.Time
+	establishSessionDuration float64
 }
 
-type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request) error
+type channelHandler func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error
 
-func newConnection(cfg *config.Config, remoteAddr string, sconn *ssh.ServerConn) *connection {
+func newConnection(cfg *config.Config, nconn net.Conn) *connection {
 	maxSessions := cfg.Server.ConcurrentSessionsLimit
 
 	return &connection{
 		cfg:                cfg,
 		maxSessions:        maxSessions,
 		concurrentSessions: semaphore.NewWeighted(maxSessions),
-		remoteAddr:         remoteAddr,
-		sconn:              sconn,
+		nconn:              nconn,
+		remoteAddr:         nconn.RemoteAddr().String(),
+		started:            time.Now(),
 	}
 }
 
-func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
-	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+func (c *connection) handle(ctx context.Context, srvCfg *ssh.ServerConfig, handler channelHandler) {
+	sconn, chans, err := c.initServerConn(ctx, srvCfg)
+	if err != nil {
+		return
+	}
 
 	if c.cfg.Server.ClientAliveInterval > 0 {
 		ticker := time.NewTicker(time.Duration(c.cfg.Server.ClientAliveInterval))
 		defer ticker.Stop()
-		go c.sendKeepAliveMsg(ctx, ticker)
+		go c.sendKeepAliveMsg(ctx, sconn, ticker)
 	}
 
+	c.handleRequests(ctx, sconn, chans, handler)
+
+	reason := sconn.Wait()
+	log.WithContextFields(ctx, log.Fields{
+		"duration_s":                   time.Since(c.started).Seconds(),
+		"establish_session_duration_s": c.establishSessionDuration,
+		"reason":                       reason,
+	}).Info("server: handleConn: done")
+}
+
+func (c *connection) initServerConn(ctx context.Context, srvCfg *ssh.ServerConfig) (*ssh.ServerConn, <-chan ssh.NewChannel, error) {
+	sconn, chans, reqs, err := ssh.NewServerConn(c.nconn, srvCfg)
+	if err != nil {
+		msg := "connection: initServerConn: failed to initialize SSH connection"
+
+		logger := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr}).WithError(err)
+
+		if strings.Contains(err.Error(), "no common algorithm for host key") || err.Error() == "EOF" {
+			logger.Debug(msg)
+		} else {
+			logger.Warn(msg)
+		}
+
+		return nil, nil, err
+	}
+	go ssh.DiscardRequests(reqs)
+
+	return sconn, chans, err
+}
+
+func (c *connection) handleRequests(ctx context.Context, sconn *ssh.ServerConn, chans <-chan ssh.NewChannel, handler channelHandler) {
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+
 	for newChannel := range chans {
 		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
-			ctxlog.Info("connection: handle: unknown channel type")
+			ctxlog.Info("connection: handleRequests: unknown channel type")
 			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
 			continue
 		}
 		if !c.concurrentSessions.TryAcquire(1) {
-			ctxlog.Info("connection: handle: too many concurrent sessions")
+			ctxlog.Info("connection: handleRequests: too many concurrent sessions")
 			newChannel.Reject(ssh.ResourceShortage, "too many concurrent sessions")
 			metrics.SshdHitMaxSessions.Inc()
 			continue
 		}
 		channel, requests, err := newChannel.Accept()
 		if err != nil {
-			ctxlog.WithError(err).Error("connection: handle: accepting channel failed")
+			ctxlog.WithError(err).Error("connection: handleRequests: accepting channel failed")
 			c.concurrentSessions.Release(1)
 			continue
 		}
@@ -76,6 +117,7 @@
 			defer func(started time.Time) {
 				metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
 			}(time.Now())
+			c.establishSessionDuration = time.Since(c.started).Seconds()
 
 			defer c.concurrentSessions.Release(1)
 
@@ -87,12 +129,12 @@
 			}()
 
 			metrics.SliSshdSessionsTotal.Inc()
-			err := handler(ctx, channel, requests)
+			err := handler(sconn, channel, requests)
 			if err != nil {
 				c.trackError(err)
 			}
 
-			ctxlog.Info("connection: handle: done")
+			ctxlog.Info("connection: handleRequests: done")
 		}()
 	}
 
@@ -105,7 +147,7 @@
 	c.concurrentSessions.Acquire(ctx, c.maxSessions)
 }
 
-func (c *connection) sendKeepAliveMsg(ctx context.Context, ticker *time.Ticker) {
+func (c *connection) sendKeepAliveMsg(ctx context.Context, sconn *ssh.ServerConn, ticker *time.Ticker) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
 	for {
@@ -113,9 +155,9 @@
 		case <-ctx.Done():
 			return
 		case <-ticker.C:
-			ctxlog.Debug("session: handleShell: send keepalive message to a client")
+			ctxlog.Debug("connection: sendKeepAliveMsg: send keepalive message to a client")
 
-			c.sconn.SendRequest(KeepAliveMsg, true, nil)
+			sconn.SendRequest(KeepAliveMsg, true, nil)
 		}
 	}
 }
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -10,6 +10,7 @@
 	"github.com/prometheus/client_golang/prometheus/testutil"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
+	"golang.org/x/sync/semaphore"
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
@@ -81,7 +82,7 @@
 
 func setup(sessionsNum int64, newChannel *fakeNewChannel) (*connection, chan ssh.NewChannel) {
 	cfg := &config.Config{Server: config.ServerConfig{ConcurrentSessionsLimit: sessionsNum}}
-	conn := newConnection(cfg, "127.0.0.1:50000", &ssh.ServerConn{&fakeConn{}, nil})
+	conn := &connection{cfg: cfg, concurrentSessions: semaphore.NewWeighted(sessionsNum)}
 
 	chans := make(chan ssh.NewChannel, 1)
 	chans <- newChannel
@@ -95,7 +96,7 @@
 
 	numSessions := 0
 	require.NotPanics(t, func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 			numSessions += 1
 			close(chans)
 			panic("This is a panic")
@@ -113,7 +114,7 @@
 	conn, chans := setup(1, newChannel)
 
 	go func() {
-		conn.handle(context.Background(), chans, nil)
+		conn.handleRequests(context.Background(), nil, chans, nil)
 	}()
 
 	rejectionData := <-rejectCh
@@ -133,7 +134,7 @@
 	defer cancel()
 
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 			<-ctx.Done() // Keep the accepted channel open until the end of the test
 			return nil
 		})
@@ -148,7 +149,7 @@
 	conn, chans := setup(1, newChannel)
 
 	channelHandled := false
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		channelHandled = true
 		close(chans)
 		return nil
@@ -167,7 +168,7 @@
 
 	channelHandled := false
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 			channelHandled = true
 			return nil
 		})
@@ -185,12 +186,11 @@
 func TestClientAliveInterval(t *testing.T) {
 	f := &fakeConn{}
 
-	conn := newConnection(&config.Config{}, "127.0.0.1:50000", &ssh.ServerConn{f, nil})
-
 	ticker := time.NewTicker(time.Millisecond)
 	defer ticker.Stop()
 
-	go conn.sendKeepAliveMsg(context.Background(), ticker)
+	conn := &connection{}
+	go conn.sendKeepAliveMsg(context.Background(), &ssh.ServerConn{f, nil}, ticker)
 
 	require.Eventually(t, func() bool { return KeepAliveMsg == f.SentRequestName() }, time.Second, time.Millisecond)
 }
@@ -204,7 +204,7 @@
 	newChannel := &fakeNewChannel{channelType: "session"}
 
 	conn, chans := setup(1, newChannel)
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
 		return errors.New("custom error")
 	})
@@ -213,7 +213,7 @@
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 
 	conn, chans = setup(1, newChannel)
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
 		return grpcstatus.Error(grpccodes.Canceled, "canceled")
 	})
@@ -222,7 +222,7 @@
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 
 	conn, chans = setup(1, newChannel)
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
 		return &client.ApiError{"api error"}
 	})
@@ -231,7 +231,7 @@
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 
 	conn, chans = setup(1, newChannel)
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
 		return grpcstatus.Error(grpccodes.Unavailable, "unavailable")
 	})
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -10,7 +10,6 @@
 	"time"
 
 	"github.com/pires/go-proxyproto"
-	"github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -39,18 +38,6 @@
 	serverConfig *serverConfig
 }
 
-func logSSHInitError(ctxlog *logrus.Entry, err error) {
-	msg := "server: handleConn: failed to initialize SSH connection"
-
-	logger := ctxlog.WithError(err)
-
-	if strings.Contains(err.Error(), "no common algorithm for host key") || err.Error() == "EOF" {
-		logger.Debug(msg)
-	} else {
-		logger.Warn(msg)
-	}
-}
-
 func NewServer(cfg *config.Config) (*Server, error) {
 	serverConfig, err := newServerConfig(cfg)
 	if err != nil {
@@ -171,6 +158,7 @@
 	defer cancel()
 
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": remoteAddr})
+	ctxlog.Debug("server: handleConn: start")
 
 	// Prevent a panic in a single connection from taking out the whole server
 	defer func() {
@@ -181,22 +169,8 @@
 		}
 	}()
 
-	ctxlog.Debug("server: handleConn: start")
-
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
-	if err != nil {
-		logSSHInitError(ctxlog, err)
-		return
-	}
-	go ssh.DiscardRequests(reqs)
-
-	started := time.Now()
-	var establishSessionDuration float64
-	conn := newConnection(s.Config, remoteAddr, sconn)
-	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) error {
-		establishSessionDuration = time.Since(started).Seconds()
-		metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
-
+	conn := newConnection(s.Config, nconn)
+	conn.handle(ctx, s.serverConfig.get(ctx), func(sconn *ssh.ServerConn, channel ssh.Channel, requests <-chan *ssh.Request) error {
 		session := &session{
 			cfg:         s.Config,
 			channel:     channel,
@@ -206,13 +180,6 @@
 
 		return session.handle(ctx, requests)
 	})
-
-	reason := sconn.Wait()
-	ctxlog.WithFields(log.Fields{
-		"duration_s":                   time.Since(started).Seconds(),
-		"establish_session_duration_s": establishSessionDuration,
-		"reason":                       reason,
-	}).Info("server: handleConn: done")
 }
 
 func (s *Server) requirePolicy(_ net.Addr) (proxyproto.Policy, error) {
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653311792 -14400
#      Mon May 23 17:16:32 2022 +0400
# Node ID 57a182223f988ee89744efb35b0edcad7dfc691a
# Parent  929d42f39088c63cb672deec90e29e8266ae64cb
Close the connection when context is canceled

When graceful shutdown timeout expires, the global context is
canceled. All the operations dependent on it are canceled as well.

Unfortunately, some of the operations doesn't respect the context.
For example, SSH connection initialization.

In this case, we need to manually close the connection.
One of the options is to wait for ctx.Done() and close the connection

diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -146,17 +146,19 @@
 }
 
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
+	defer s.wg.Done()
+
 	metrics.SshdConnectionsInFlight.Inc()
 	defer metrics.SshdConnectionsInFlight.Dec()
 
-	remoteAddr := nconn.RemoteAddr().String()
-
-	defer s.wg.Done()
-	defer nconn.Close()
-
 	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
 	defer cancel()
+	go func() {
+		<-ctx.Done()
+		nconn.Close() // Close the connection when context is cancelled
+	}()
 
+	remoteAddr := nconn.RemoteAddr().String()
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": remoteAddr})
 	ctxlog.Debug("server: handleConn: start")
 
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -222,6 +222,35 @@
 	require.Nil(t, s.Shutdown())
 }
 
+func TestClosingHangedConnections(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	s := setupServerWithContext(t, nil, ctx)
+
+	unauthenticatedRequestStatus := make(chan string)
+	completed := make(chan bool)
+
+	clientCfg := clientConfig(t)
+	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
+		unauthenticatedRequestStatus <- "authentication-started"
+		<-completed // Wait infinitely
+
+		return nil
+	}
+
+	go func() {
+		// Start an SSH connection that never ends
+		ssh.Dial("tcp", serverUrl, clientCfg)
+	}()
+
+	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
+
+	require.NoError(t, s.Shutdown())
+	cancel()
+	verifyStatus(t, s, StatusClosed)
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
@@ -231,6 +260,12 @@
 func setupServerWithConfig(t *testing.T, cfg *config.Config) *Server {
 	t.Helper()
 
+	return setupServerWithContext(t, cfg, context.Background())
+}
+
+func setupServerWithContext(t *testing.T, cfg *config.Config, ctx context.Context) *Server {
+	t.Helper()
+
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/authorized_keys",
@@ -270,7 +305,7 @@
 	s, err := NewServer(cfg)
 	require.NoError(t, err)
 
-	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
+	go func() { require.NoError(t, s.ListenAndServe(ctx)) }()
 	t.Cleanup(func() { s.Shutdown() })
 
 	verifyStatus(t, s, StatusReady)
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1653325222 0
#      Mon May 23 17:00:22 2022 +0000
# Node ID 58aa9e1be5d3b8b3764a6b9faec5ac47ee4d1070
# Parent  7388be2b682515483bb91b68a351c5b0d2590c17
# Parent  57a182223f988ee89744efb35b0edcad7dfc691a
Merge branch 'id-login-grace-time' into 'main'

Close the connection when context is canceled

See merge request gitlab-org/gitlab-shell!646

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -3,6 +3,8 @@
 import (
 	"context"
 	"errors"
+	"net"
+	"strings"
 	"time"
 
 	"golang.org/x/crypto/ssh"
@@ -22,52 +24,91 @@
 var EOFTimeout = 10 * time.Second
 
 type connection struct {
-	cfg                *config.Config
-	concurrentSessions *semaphore.Weighted
-	remoteAddr         string
-	sconn              *ssh.ServerConn
-	maxSessions        int64
+	cfg                      *config.Config
+	concurrentSessions       *semaphore.Weighted
+	nconn                    net.Conn
+	maxSessions              int64
+	remoteAddr               string
+	started                  time.Time
+	establishSessionDuration float64
 }
 
-type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request) error
+type channelHandler func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error
 
-func newConnection(cfg *config.Config, remoteAddr string, sconn *ssh.ServerConn) *connection {
+func newConnection(cfg *config.Config, nconn net.Conn) *connection {
 	maxSessions := cfg.Server.ConcurrentSessionsLimit
 
 	return &connection{
 		cfg:                cfg,
 		maxSessions:        maxSessions,
 		concurrentSessions: semaphore.NewWeighted(maxSessions),
-		remoteAddr:         remoteAddr,
-		sconn:              sconn,
+		nconn:              nconn,
+		remoteAddr:         nconn.RemoteAddr().String(),
+		started:            time.Now(),
 	}
 }
 
-func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
-	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+func (c *connection) handle(ctx context.Context, srvCfg *ssh.ServerConfig, handler channelHandler) {
+	sconn, chans, err := c.initServerConn(ctx, srvCfg)
+	if err != nil {
+		return
+	}
 
 	if c.cfg.Server.ClientAliveInterval > 0 {
 		ticker := time.NewTicker(time.Duration(c.cfg.Server.ClientAliveInterval))
 		defer ticker.Stop()
-		go c.sendKeepAliveMsg(ctx, ticker)
+		go c.sendKeepAliveMsg(ctx, sconn, ticker)
 	}
 
+	c.handleRequests(ctx, sconn, chans, handler)
+
+	reason := sconn.Wait()
+	log.WithContextFields(ctx, log.Fields{
+		"duration_s":                   time.Since(c.started).Seconds(),
+		"establish_session_duration_s": c.establishSessionDuration,
+		"reason":                       reason,
+	}).Info("server: handleConn: done")
+}
+
+func (c *connection) initServerConn(ctx context.Context, srvCfg *ssh.ServerConfig) (*ssh.ServerConn, <-chan ssh.NewChannel, error) {
+	sconn, chans, reqs, err := ssh.NewServerConn(c.nconn, srvCfg)
+	if err != nil {
+		msg := "connection: initServerConn: failed to initialize SSH connection"
+
+		logger := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr}).WithError(err)
+
+		if strings.Contains(err.Error(), "no common algorithm for host key") || err.Error() == "EOF" {
+			logger.Debug(msg)
+		} else {
+			logger.Warn(msg)
+		}
+
+		return nil, nil, err
+	}
+	go ssh.DiscardRequests(reqs)
+
+	return sconn, chans, err
+}
+
+func (c *connection) handleRequests(ctx context.Context, sconn *ssh.ServerConn, chans <-chan ssh.NewChannel, handler channelHandler) {
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+
 	for newChannel := range chans {
 		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
-			ctxlog.Info("connection: handle: unknown channel type")
+			ctxlog.Info("connection: handleRequests: unknown channel type")
 			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
 			continue
 		}
 		if !c.concurrentSessions.TryAcquire(1) {
-			ctxlog.Info("connection: handle: too many concurrent sessions")
+			ctxlog.Info("connection: handleRequests: too many concurrent sessions")
 			newChannel.Reject(ssh.ResourceShortage, "too many concurrent sessions")
 			metrics.SshdHitMaxSessions.Inc()
 			continue
 		}
 		channel, requests, err := newChannel.Accept()
 		if err != nil {
-			ctxlog.WithError(err).Error("connection: handle: accepting channel failed")
+			ctxlog.WithError(err).Error("connection: handleRequests: accepting channel failed")
 			c.concurrentSessions.Release(1)
 			continue
 		}
@@ -76,6 +117,7 @@
 			defer func(started time.Time) {
 				metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
 			}(time.Now())
+			c.establishSessionDuration = time.Since(c.started).Seconds()
 
 			defer c.concurrentSessions.Release(1)
 
@@ -87,12 +129,12 @@
 			}()
 
 			metrics.SliSshdSessionsTotal.Inc()
-			err := handler(ctx, channel, requests)
+			err := handler(sconn, channel, requests)
 			if err != nil {
 				c.trackError(err)
 			}
 
-			ctxlog.Info("connection: handle: done")
+			ctxlog.Info("connection: handleRequests: done")
 		}()
 	}
 
@@ -105,7 +147,7 @@
 	c.concurrentSessions.Acquire(ctx, c.maxSessions)
 }
 
-func (c *connection) sendKeepAliveMsg(ctx context.Context, ticker *time.Ticker) {
+func (c *connection) sendKeepAliveMsg(ctx context.Context, sconn *ssh.ServerConn, ticker *time.Ticker) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
 	for {
@@ -113,9 +155,9 @@
 		case <-ctx.Done():
 			return
 		case <-ticker.C:
-			ctxlog.Debug("session: handleShell: send keepalive message to a client")
+			ctxlog.Debug("connection: sendKeepAliveMsg: send keepalive message to a client")
 
-			c.sconn.SendRequest(KeepAliveMsg, true, nil)
+			sconn.SendRequest(KeepAliveMsg, true, nil)
 		}
 	}
 }
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -10,6 +10,7 @@
 	"github.com/prometheus/client_golang/prometheus/testutil"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
+	"golang.org/x/sync/semaphore"
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
@@ -81,7 +82,7 @@
 
 func setup(sessionsNum int64, newChannel *fakeNewChannel) (*connection, chan ssh.NewChannel) {
 	cfg := &config.Config{Server: config.ServerConfig{ConcurrentSessionsLimit: sessionsNum}}
-	conn := newConnection(cfg, "127.0.0.1:50000", &ssh.ServerConn{&fakeConn{}, nil})
+	conn := &connection{cfg: cfg, concurrentSessions: semaphore.NewWeighted(sessionsNum)}
 
 	chans := make(chan ssh.NewChannel, 1)
 	chans <- newChannel
@@ -95,7 +96,7 @@
 
 	numSessions := 0
 	require.NotPanics(t, func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 			numSessions += 1
 			close(chans)
 			panic("This is a panic")
@@ -113,7 +114,7 @@
 	conn, chans := setup(1, newChannel)
 
 	go func() {
-		conn.handle(context.Background(), chans, nil)
+		conn.handleRequests(context.Background(), nil, chans, nil)
 	}()
 
 	rejectionData := <-rejectCh
@@ -133,7 +134,7 @@
 	defer cancel()
 
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 			<-ctx.Done() // Keep the accepted channel open until the end of the test
 			return nil
 		})
@@ -148,7 +149,7 @@
 	conn, chans := setup(1, newChannel)
 
 	channelHandled := false
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		channelHandled = true
 		close(chans)
 		return nil
@@ -167,7 +168,7 @@
 
 	channelHandled := false
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 			channelHandled = true
 			return nil
 		})
@@ -185,12 +186,11 @@
 func TestClientAliveInterval(t *testing.T) {
 	f := &fakeConn{}
 
-	conn := newConnection(&config.Config{}, "127.0.0.1:50000", &ssh.ServerConn{f, nil})
-
 	ticker := time.NewTicker(time.Millisecond)
 	defer ticker.Stop()
 
-	go conn.sendKeepAliveMsg(context.Background(), ticker)
+	conn := &connection{}
+	go conn.sendKeepAliveMsg(context.Background(), &ssh.ServerConn{f, nil}, ticker)
 
 	require.Eventually(t, func() bool { return KeepAliveMsg == f.SentRequestName() }, time.Second, time.Millisecond)
 }
@@ -204,7 +204,7 @@
 	newChannel := &fakeNewChannel{channelType: "session"}
 
 	conn, chans := setup(1, newChannel)
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
 		return errors.New("custom error")
 	})
@@ -213,7 +213,7 @@
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 
 	conn, chans = setup(1, newChannel)
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
 		return grpcstatus.Error(grpccodes.Canceled, "canceled")
 	})
@@ -222,7 +222,7 @@
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 
 	conn, chans = setup(1, newChannel)
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
 		return &client.ApiError{"api error"}
 	})
@@ -231,7 +231,7 @@
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 
 	conn, chans = setup(1, newChannel)
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
 		return grpcstatus.Error(grpccodes.Unavailable, "unavailable")
 	})
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -10,7 +10,6 @@
 	"time"
 
 	"github.com/pires/go-proxyproto"
-	"github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -39,18 +38,6 @@
 	serverConfig *serverConfig
 }
 
-func logSSHInitError(ctxlog *logrus.Entry, err error) {
-	msg := "server: handleConn: failed to initialize SSH connection"
-
-	logger := ctxlog.WithError(err)
-
-	if strings.Contains(err.Error(), "no common algorithm for host key") || err.Error() == "EOF" {
-		logger.Debug(msg)
-	} else {
-		logger.Warn(msg)
-	}
-}
-
 func NewServer(cfg *config.Config) (*Server, error) {
 	serverConfig, err := newServerConfig(cfg)
 	if err != nil {
@@ -159,18 +146,21 @@
 }
 
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
+	defer s.wg.Done()
+
 	metrics.SshdConnectionsInFlight.Inc()
 	defer metrics.SshdConnectionsInFlight.Dec()
 
-	remoteAddr := nconn.RemoteAddr().String()
-
-	defer s.wg.Done()
-	defer nconn.Close()
-
 	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
 	defer cancel()
+	go func() {
+		<-ctx.Done()
+		nconn.Close() // Close the connection when context is cancelled
+	}()
 
+	remoteAddr := nconn.RemoteAddr().String()
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": remoteAddr})
+	ctxlog.Debug("server: handleConn: start")
 
 	// Prevent a panic in a single connection from taking out the whole server
 	defer func() {
@@ -181,22 +171,8 @@
 		}
 	}()
 
-	ctxlog.Debug("server: handleConn: start")
-
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
-	if err != nil {
-		logSSHInitError(ctxlog, err)
-		return
-	}
-	go ssh.DiscardRequests(reqs)
-
-	started := time.Now()
-	var establishSessionDuration float64
-	conn := newConnection(s.Config, remoteAddr, sconn)
-	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) error {
-		establishSessionDuration = time.Since(started).Seconds()
-		metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
-
+	conn := newConnection(s.Config, nconn)
+	conn.handle(ctx, s.serverConfig.get(ctx), func(sconn *ssh.ServerConn, channel ssh.Channel, requests <-chan *ssh.Request) error {
 		session := &session{
 			cfg:         s.Config,
 			channel:     channel,
@@ -206,13 +182,6 @@
 
 		return session.handle(ctx, requests)
 	})
-
-	reason := sconn.Wait()
-	ctxlog.WithFields(log.Fields{
-		"duration_s":                   time.Since(started).Seconds(),
-		"establish_session_duration_s": establishSessionDuration,
-		"reason":                       reason,
-	}).Info("server: handleConn: done")
 }
 
 func (s *Server) requirePolicy(_ net.Addr) (proxyproto.Policy, error) {
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -222,6 +222,35 @@
 	require.Nil(t, s.Shutdown())
 }
 
+func TestClosingHangedConnections(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	s := setupServerWithContext(t, nil, ctx)
+
+	unauthenticatedRequestStatus := make(chan string)
+	completed := make(chan bool)
+
+	clientCfg := clientConfig(t)
+	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
+		unauthenticatedRequestStatus <- "authentication-started"
+		<-completed // Wait infinitely
+
+		return nil
+	}
+
+	go func() {
+		// Start an SSH connection that never ends
+		ssh.Dial("tcp", serverUrl, clientCfg)
+	}()
+
+	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
+
+	require.NoError(t, s.Shutdown())
+	cancel()
+	verifyStatus(t, s, StatusClosed)
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
@@ -231,6 +260,12 @@
 func setupServerWithConfig(t *testing.T, cfg *config.Config) *Server {
 	t.Helper()
 
+	return setupServerWithContext(t, cfg, context.Background())
+}
+
+func setupServerWithContext(t *testing.T, cfg *config.Config, ctx context.Context) *Server {
+	t.Helper()
+
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/authorized_keys",
@@ -270,7 +305,7 @@
 	s, err := NewServer(cfg)
 	require.NoError(t, err)
 
-	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
+	go func() { require.NoError(t, s.ListenAndServe(ctx)) }()
 	t.Cleanup(func() { s.Shutdown() })
 
 	verifyStatus(t, s, StatusReady)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653318594 -14400
#      Mon May 23 19:09:54 2022 +0400
# Node ID 72603ad5e8b7a2bad70ed13870e6d63e24f88e95
# Parent  58aa9e1be5d3b8b3764a6b9faec5ac47ee4d1070
Abort long-running unauthenticated SSH connections

The config option is basically a copy of LoginGraceTime OpenSSH
option.

If an SSH connection is hanging unauthenticated, after some period
of time, the connection gets canceled. The value is configurable,
the server waits for 60 seconds by default.

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -76,10 +76,12 @@
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
-  # Sets an interval after which server will send keepalive message to a client
+  # Sets an interval after which server will send keepalive message to a client. Defaults to 15s.
   client_alive_interval: 15
-  # The server waits for this time (in seconds) for the ongoing connections to complete before shutting down. Defaults to 10.
+  # The server waits for this time for the ongoing connections to complete before shutting down. Defaults to 10s.
   grace_period: 10
+  # The server disconnects after this time if the user has not successfully logged in. Defaults to 60s.
+  login_grace_time: 60
   # A short timeout to decide to abort the connection if the protocol header is not seen within it. Defaults to 500ms
   proxy_header_timeout: 500ms
   # The endpoint that returns 200 OK if the server is ready to receive incoming connections; otherwise, it returns 503 Service Unavailable. Defaults to "/start".
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -21,7 +21,7 @@
 	defaultSecretFileName = ".gitlab_shell_secret"
 )
 
-type yamlDuration time.Duration
+type YamlDuration time.Duration
 
 type ServerConfig struct {
 	Listen                  string       `yaml:"listen,omitempty"`
@@ -29,9 +29,10 @@
 	ProxyPolicy             string       `yaml:"proxy_policy,omitempty"`
 	WebListen               string       `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64        `yaml:"concurrent_sessions_limit,omitempty"`
-	ClientAliveInterval     yamlDuration `yaml:"client_alive_interval,omitempty"`
-	GracePeriod             yamlDuration `yaml:"grace_period"`
-	ProxyHeaderTimeout      yamlDuration `yaml:"proxy_header_timeout"`
+	ClientAliveInterval     YamlDuration `yaml:"client_alive_interval,omitempty"`
+	GracePeriod             YamlDuration `yaml:"grace_period"`
+	ProxyHeaderTimeout      YamlDuration `yaml:"proxy_header_timeout"`
+	LoginGraceTime          YamlDuration `yaml:"login_grace_time"`
 	ReadinessProbe          string       `yaml:"readiness_probe"`
 	LivenessProbe           string       `yaml:"liveness_probe"`
 	HostKeyFiles            []string     `yaml:"host_key_files,omitempty"`
@@ -85,9 +86,10 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
-		GracePeriod:             yamlDuration(10 * time.Second),
-		ClientAliveInterval:     yamlDuration(15 * time.Second),
-		ProxyHeaderTimeout:      yamlDuration(500 * time.Millisecond),
+		GracePeriod:             YamlDuration(10 * time.Second),
+		ClientAliveInterval:     YamlDuration(15 * time.Second),
+		ProxyHeaderTimeout:      YamlDuration(500 * time.Millisecond),
+		LoginGraceTime:          YamlDuration(60 * time.Second),
 		ReadinessProbe:          "/start",
 		LivenessProbe:           "/health",
 		HostKeyFiles: []string{
@@ -98,13 +100,13 @@
 	}
 )
 
-func (d *yamlDuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
+func (d *YamlDuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	var intDuration int
 	if err := unmarshal(&intDuration); err != nil {
 		return unmarshal((*time.Duration)(d))
 	}
 
-	*d = yamlDuration(time.Duration(intDuration) * time.Second)
+	*d = YamlDuration(time.Duration(intDuration) * time.Second)
 
 	return nil
 }
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -83,7 +83,7 @@
 	}
 
 	type durationCfg struct {
-		Duration yamlDuration `yaml:"duration"`
+		Duration YamlDuration `yaml:"duration"`
 	}
 
 	for _, tc := range testCases {
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -71,10 +71,14 @@
 }
 
 func (c *connection) initServerConn(ctx context.Context, srvCfg *ssh.ServerConfig) (*ssh.ServerConn, <-chan ssh.NewChannel, error) {
+	if c.cfg.Server.LoginGraceTime > 0 {
+		c.nconn.SetDeadline(time.Now().Add(time.Duration(c.cfg.Server.LoginGraceTime)))
+		defer c.nconn.SetDeadline(time.Time{})
+	}
+
 	sconn, chans, reqs, err := ssh.NewServerConn(c.nconn, srvCfg)
 	if err != nil {
 		msg := "connection: initServerConn: failed to initialize SSH connection"
-
 		logger := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr}).WithError(err)
 
 		if strings.Contains(err.Error(), "no common algorithm for host key") || err.Error() == "EOF" {
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -251,6 +251,39 @@
 	verifyStatus(t, s, StatusClosed)
 }
 
+func TestLoginGraceTime(t *testing.T) {
+	cfg := &config.Config{
+		Server: config.ServerConfig{
+			LoginGraceTime: config.YamlDuration(50 * time.Millisecond),
+		},
+	}
+	s := setupServerWithConfig(t, cfg)
+
+	unauthenticatedRequestStatus := make(chan string)
+	completed := make(chan bool)
+
+	clientCfg := clientConfig(t)
+	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
+		unauthenticatedRequestStatus <- "authentication-started"
+		<-completed // Wait infinitely
+
+		return nil
+	}
+
+	go func() {
+		// Start an SSH connection that never ends
+		ssh.Dial("tcp", serverUrl, clientCfg)
+	}()
+
+	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
+
+	// Shutdown the server and verify that it's closed
+	// If LoginGraceTime doesn't work, then the connection that runs infinitely, will stop it from closing.
+	// The close won't happen until the context is canceled like in TestClosingHangedConnections
+	require.NoError(t, s.Shutdown())
+	verifyStatus(t, s, StatusClosed)
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653329107 -14400
#      Mon May 23 22:05:07 2022 +0400
# Node ID 6daa11cf78d408c6f80f47dc58cc0c0255a55d74
# Parent  72603ad5e8b7a2bad70ed13870e6d63e24f88e95
Add missing SshdSessionEstablishedDuration metrics

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -122,6 +122,7 @@
 				metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
 			}(time.Now())
 			c.establishSessionDuration = time.Since(c.started).Seconds()
+			metrics.SshdSessionEstablishedDuration.Observe(c.establishSessionDuration)
 
 			defer c.concurrentSessions.Release(1)
 
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1653334249 0
#      Mon May 23 19:30:49 2022 +0000
# Node ID c9b3de80460bdb418a34a53e4be6011c28f06546
# Parent  58aa9e1be5d3b8b3764a6b9faec5ac47ee4d1070
# Parent  6daa11cf78d408c6f80f47dc58cc0c0255a55d74
Merge branch 'id-login-grace-time-impl' into 'main'

Abort long-running unauthenticated SSH connections

See merge request gitlab-org/gitlab-shell!647

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -76,10 +76,12 @@
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
-  # Sets an interval after which server will send keepalive message to a client
+  # Sets an interval after which server will send keepalive message to a client. Defaults to 15s.
   client_alive_interval: 15
-  # The server waits for this time (in seconds) for the ongoing connections to complete before shutting down. Defaults to 10.
+  # The server waits for this time for the ongoing connections to complete before shutting down. Defaults to 10s.
   grace_period: 10
+  # The server disconnects after this time if the user has not successfully logged in. Defaults to 60s.
+  login_grace_time: 60
   # A short timeout to decide to abort the connection if the protocol header is not seen within it. Defaults to 500ms
   proxy_header_timeout: 500ms
   # The endpoint that returns 200 OK if the server is ready to receive incoming connections; otherwise, it returns 503 Service Unavailable. Defaults to "/start".
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -21,7 +21,7 @@
 	defaultSecretFileName = ".gitlab_shell_secret"
 )
 
-type yamlDuration time.Duration
+type YamlDuration time.Duration
 
 type ServerConfig struct {
 	Listen                  string       `yaml:"listen,omitempty"`
@@ -29,9 +29,10 @@
 	ProxyPolicy             string       `yaml:"proxy_policy,omitempty"`
 	WebListen               string       `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64        `yaml:"concurrent_sessions_limit,omitempty"`
-	ClientAliveInterval     yamlDuration `yaml:"client_alive_interval,omitempty"`
-	GracePeriod             yamlDuration `yaml:"grace_period"`
-	ProxyHeaderTimeout      yamlDuration `yaml:"proxy_header_timeout"`
+	ClientAliveInterval     YamlDuration `yaml:"client_alive_interval,omitempty"`
+	GracePeriod             YamlDuration `yaml:"grace_period"`
+	ProxyHeaderTimeout      YamlDuration `yaml:"proxy_header_timeout"`
+	LoginGraceTime          YamlDuration `yaml:"login_grace_time"`
 	ReadinessProbe          string       `yaml:"readiness_probe"`
 	LivenessProbe           string       `yaml:"liveness_probe"`
 	HostKeyFiles            []string     `yaml:"host_key_files,omitempty"`
@@ -85,9 +86,10 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
-		GracePeriod:             yamlDuration(10 * time.Second),
-		ClientAliveInterval:     yamlDuration(15 * time.Second),
-		ProxyHeaderTimeout:      yamlDuration(500 * time.Millisecond),
+		GracePeriod:             YamlDuration(10 * time.Second),
+		ClientAliveInterval:     YamlDuration(15 * time.Second),
+		ProxyHeaderTimeout:      YamlDuration(500 * time.Millisecond),
+		LoginGraceTime:          YamlDuration(60 * time.Second),
 		ReadinessProbe:          "/start",
 		LivenessProbe:           "/health",
 		HostKeyFiles: []string{
@@ -98,13 +100,13 @@
 	}
 )
 
-func (d *yamlDuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
+func (d *YamlDuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	var intDuration int
 	if err := unmarshal(&intDuration); err != nil {
 		return unmarshal((*time.Duration)(d))
 	}
 
-	*d = yamlDuration(time.Duration(intDuration) * time.Second)
+	*d = YamlDuration(time.Duration(intDuration) * time.Second)
 
 	return nil
 }
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -83,7 +83,7 @@
 	}
 
 	type durationCfg struct {
-		Duration yamlDuration `yaml:"duration"`
+		Duration YamlDuration `yaml:"duration"`
 	}
 
 	for _, tc := range testCases {
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -71,10 +71,14 @@
 }
 
 func (c *connection) initServerConn(ctx context.Context, srvCfg *ssh.ServerConfig) (*ssh.ServerConn, <-chan ssh.NewChannel, error) {
+	if c.cfg.Server.LoginGraceTime > 0 {
+		c.nconn.SetDeadline(time.Now().Add(time.Duration(c.cfg.Server.LoginGraceTime)))
+		defer c.nconn.SetDeadline(time.Time{})
+	}
+
 	sconn, chans, reqs, err := ssh.NewServerConn(c.nconn, srvCfg)
 	if err != nil {
 		msg := "connection: initServerConn: failed to initialize SSH connection"
-
 		logger := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr}).WithError(err)
 
 		if strings.Contains(err.Error(), "no common algorithm for host key") || err.Error() == "EOF" {
@@ -118,6 +122,7 @@
 				metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
 			}(time.Now())
 			c.establishSessionDuration = time.Since(c.started).Seconds()
+			metrics.SshdSessionEstablishedDuration.Observe(c.establishSessionDuration)
 
 			defer c.concurrentSessions.Release(1)
 
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -251,6 +251,39 @@
 	verifyStatus(t, s, StatusClosed)
 }
 
+func TestLoginGraceTime(t *testing.T) {
+	cfg := &config.Config{
+		Server: config.ServerConfig{
+			LoginGraceTime: config.YamlDuration(50 * time.Millisecond),
+		},
+	}
+	s := setupServerWithConfig(t, cfg)
+
+	unauthenticatedRequestStatus := make(chan string)
+	completed := make(chan bool)
+
+	clientCfg := clientConfig(t)
+	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
+		unauthenticatedRequestStatus <- "authentication-started"
+		<-completed // Wait infinitely
+
+		return nil
+	}
+
+	go func() {
+		// Start an SSH connection that never ends
+		ssh.Dial("tcp", serverUrl, clientCfg)
+	}()
+
+	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
+
+	// Shutdown the server and verify that it's closed
+	// If LoginGraceTime doesn't work, then the connection that runs infinitely, will stop it from closing.
+	// The close won't happen until the context is canceled like in TestClosingHangedConnections
+	require.NoError(t, s.Shutdown())
+	verifyStatus(t, s, StatusClosed)
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1653334375 25200
#      Mon May 23 12:32:55 2022 -0700
# Node ID 2cebb1acbf5cbb82111a48cb8ecf89ffd38c5d16
# Parent  c9b3de80460bdb418a34a53e4be6011c28f06546
Release v14.7.0

- Abort long-running unauthenticated SSH connections !647
- Close the connection when context is canceled !646

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v14.7.0
+
+- Abort long-running unauthenticated SSH connections !647
+- Close the connection when context is canceled !646
+
 v14.6.1
 
 - Return support for diffie-hellman-group14-sha1 !644
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.6.1
+14.7.0
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1653334810 0
#      Mon May 23 19:40:10 2022 +0000
# Node ID c54d0c6dfdb2076fb1a80dfd40886d8d98eb9a98
# Parent  c9b3de80460bdb418a34a53e4be6011c28f06546
# Parent  2cebb1acbf5cbb82111a48cb8ecf89ffd38c5d16
Merge branch 'sh-release-14.7.0' into 'main'

Release v14.7.0

See merge request gitlab-org/gitlab-shell!648

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v14.7.0
+
+- Abort long-running unauthenticated SSH connections !647
+- Close the connection when context is canceled !646
+
 v14.6.1
 
 - Return support for diffie-hellman-group14-sha1 !644
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.6.1
+14.7.0
# HG changeset patch
# User Sean Carroll <scarroll@gitlab.com>
# Date 1652865840 -7200
#      Wed May 18 11:24:00 2022 +0200
# Node ID 798354f81cbc785a0af65eee9257a986664295dc
# Parent  95f105751282580f695ff4bec8577de261720bbd
Document gitlab-shell on GitLab SaaS

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -52,6 +52,44 @@
 
 There is also a rate-limiter in place in Gitaly, but the calls will never be made to Gitaly if the rate limit is exceeded in Gitlab Shell (Rails).
 
+## GitLab SaaS
+
+A diagram of the flow of `gitlab-shell` on GitLab.com:
+
+```mermaid
+graph LR
+    a2 --> b2
+    a2  --> b3
+    a2 --> b4
+    b2 --> c1
+    b3 --> c1
+    b4 --> c1
+    c2 --> d1
+    c2 --> d2
+    c2 --> d3
+    d1 --> e1
+    d2 --> e1
+    d3 --> e1
+    a1[Cloudflare] --> a2[TCP<br/> load balancer]
+    e1[Git]
+
+    subgraph HAProxy Fleet
+    b2[HAProxy]
+    b3[HAProxy]
+    b4[HAProxy]
+    end
+
+    subgraph GKE
+    c1[Internal TCP<br/> load balancer<br/>port 2222] --> c2[GitLab-shell<br/> pods]
+    end
+
+    subgraph Gitaly
+    d1[Gitaly]
+    d2[Gitaly]
+    d3[Gitaly]
+    end
+```
+
 ## Releasing
 
 See [PROCESS.md](./PROCESS.md)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653385846 0
#      Tue May 24 09:50:46 2022 +0000
# Node ID 86e1d0dc34802c5fd17f5f5248e69ec3bbdf1fc1
# Parent  c54d0c6dfdb2076fb1a80dfd40886d8d98eb9a98
# Parent  798354f81cbc785a0af65eee9257a986664295dc
Merge branch 'prod' into 'main'

Document gitlab-shell on GitLab SaaS

See merge request gitlab-org/gitlab-shell!625

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -52,6 +52,44 @@
 
 There is also a rate-limiter in place in Gitaly, but the calls will never be made to Gitaly if the rate limit is exceeded in Gitlab Shell (Rails).
 
+## GitLab SaaS
+
+A diagram of the flow of `gitlab-shell` on GitLab.com:
+
+```mermaid
+graph LR
+    a2 --> b2
+    a2  --> b3
+    a2 --> b4
+    b2 --> c1
+    b3 --> c1
+    b4 --> c1
+    c2 --> d1
+    c2 --> d2
+    c2 --> d3
+    d1 --> e1
+    d2 --> e1
+    d3 --> e1
+    a1[Cloudflare] --> a2[TCP<br/> load balancer]
+    e1[Git]
+
+    subgraph HAProxy Fleet
+    b2[HAProxy]
+    b3[HAProxy]
+    b4[HAProxy]
+    end
+
+    subgraph GKE
+    c1[Internal TCP<br/> load balancer<br/>port 2222] --> c2[GitLab-shell<br/> pods]
+    end
+
+    subgraph Gitaly
+    d1[Gitaly]
+    d2[Gitaly]
+    d3[Gitaly]
+    end
+```
+
 ## Releasing
 
 See [PROCESS.md](./PROCESS.md)
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1653431447 25200
#      Tue May 24 15:30:47 2022 -0700
# Node ID 6a3edabded6c36eb3818e27c0817a62b252da147
# Parent  86e1d0dc34802c5fd17f5f5248e69ec3bbdf1fc1
Log gitlab-sshd session level indicator errors

In production, we saw gitlab-sshd error metrics rise, but it was not
clear why. We now log a message every time we encounter a session
error that affects the service level indicator counter.

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -7,6 +7,7 @@
 	"strings"
 	"time"
 
+	"github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
 	grpccodes "google.golang.org/grpc/codes"
@@ -136,7 +137,7 @@
 			metrics.SliSshdSessionsTotal.Inc()
 			err := handler(sconn, channel, requests)
 			if err != nil {
-				c.trackError(err)
+				c.trackError(ctxlog, err)
 			}
 
 			ctxlog.Info("connection: handleRequests: done")
@@ -167,7 +168,7 @@
 	}
 }
 
-func (c *connection) trackError(err error) {
+func (c *connection) trackError(ctxlog *logrus.Entry, err error) {
 	var apiError *client.ApiError
 	if errors.As(err, &apiError) {
 		return
@@ -179,4 +180,5 @@
 	}
 
 	metrics.SliSshdSessionsErrorsTotal.Inc()
+	ctxlog.WithError(err).Warn("connection: session error")
 }
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653447596 0
#      Wed May 25 02:59:56 2022 +0000
# Node ID 07b38ad716c3a4de5f8d3c411704fa03e3710120
# Parent  86e1d0dc34802c5fd17f5f5248e69ec3bbdf1fc1
# Parent  6a3edabded6c36eb3818e27c0817a62b252da147
Merge branch 'sh-log-session-errors' into 'main'

Log gitlab-sshd session level indicator errors

See merge request gitlab-org/gitlab-shell!650

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -7,6 +7,7 @@
 	"strings"
 	"time"
 
+	"github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
 	grpccodes "google.golang.org/grpc/codes"
@@ -136,7 +137,7 @@
 			metrics.SliSshdSessionsTotal.Inc()
 			err := handler(sconn, channel, requests)
 			if err != nil {
-				c.trackError(err)
+				c.trackError(ctxlog, err)
 			}
 
 			ctxlog.Info("connection: handleRequests: done")
@@ -167,7 +168,7 @@
 	}
 }
 
-func (c *connection) trackError(err error) {
+func (c *connection) trackError(ctxlog *logrus.Entry, err error) {
 	var apiError *client.ApiError
 	if errors.As(err, &apiError) {
 		return
@@ -179,4 +180,5 @@
 	}
 
 	metrics.SliSshdSessionsErrorsTotal.Inc()
+	ctxlog.WithError(err).Warn("connection: session error")
 }
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653448632 -14400
#      Wed May 25 07:17:12 2022 +0400
# Node ID e3ba7fb0b513299d331e32126b7a477fd6c867fc
# Parent  86e1d0dc34802c5fd17f5f5248e69ec3bbdf1fc1
Improve establish session duration metrics

Before we took into account the time a user takes to authenticate
Now it only measures the time between a connection established and
a command started to being executed

It's still can be controlled by a user, but it's something we can
measure and restrict if necessary

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -24,13 +24,11 @@
 var EOFTimeout = 10 * time.Second
 
 type connection struct {
-	cfg                      *config.Config
-	concurrentSessions       *semaphore.Weighted
-	nconn                    net.Conn
-	maxSessions              int64
-	remoteAddr               string
-	started                  time.Time
-	establishSessionDuration float64
+	cfg                *config.Config
+	concurrentSessions *semaphore.Weighted
+	nconn              net.Conn
+	maxSessions        int64
+	remoteAddr         string
 }
 
 type channelHandler func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error
@@ -44,7 +42,6 @@
 		concurrentSessions: semaphore.NewWeighted(maxSessions),
 		nconn:              nconn,
 		remoteAddr:         nconn.RemoteAddr().String(),
-		started:            time.Now(),
 	}
 }
 
@@ -63,11 +60,7 @@
 	c.handleRequests(ctx, sconn, chans, handler)
 
 	reason := sconn.Wait()
-	log.WithContextFields(ctx, log.Fields{
-		"duration_s":                   time.Since(c.started).Seconds(),
-		"establish_session_duration_s": c.establishSessionDuration,
-		"reason":                       reason,
-	}).Info("server: handleConn: done")
+	log.WithContextFields(ctx, log.Fields{"reason": reason}).Info("server: handleConn: done")
 }
 
 func (c *connection) initServerConn(ctx context.Context, srvCfg *ssh.ServerConfig) (*ssh.ServerConn, <-chan ssh.NewChannel, error) {
@@ -119,10 +112,10 @@
 
 		go func() {
 			defer func(started time.Time) {
-				metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
+				duration := time.Since(started).Seconds()
+				metrics.SshdSessionDuration.Observe(duration)
+				ctxlog.WithFields(log.Fields{"duration_s": duration}).Info("connection: handleRequests: done")
 			}(time.Now())
-			c.establishSessionDuration = time.Since(c.started).Seconds()
-			metrics.SshdSessionEstablishedDuration.Observe(c.establishSessionDuration)
 
 			defer c.concurrentSessions.Release(1)
 
@@ -138,8 +131,6 @@
 			if err != nil {
 				c.trackError(err)
 			}
-
-			ctxlog.Info("connection: handleRequests: done")
 		}()
 	}
 
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -5,6 +5,7 @@
 	"errors"
 	"fmt"
 	"reflect"
+	"time"
 
 	"gitlab.com/gitlab-org/labkit/log"
 	"golang.org/x/crypto/ssh"
@@ -16,6 +17,7 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
@@ -29,6 +31,7 @@
 	// State managed by the session
 	execCmd            string
 	gitProtocolVersion string
+	started            time.Time
 }
 
 type execRequest struct {
@@ -171,7 +174,12 @@
 	}
 
 	cmdName := reflect.TypeOf(cmd).String()
-	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("session: handleShell: executing command")
+
+	establishSessionDuration := time.Since(s.started).Seconds()
+	ctxlog.WithFields(log.Fields{
+		"env": env, "command": cmdName, "established_session_duration_s": establishSessionDuration,
+	}).Info("session: handleShell: executing command")
+	metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
 
 	if err := cmd.Execute(ctx); err != nil {
 		grpcStatus := grpcstatus.Convert(err)
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -171,6 +171,7 @@
 		}
 	}()
 
+	started := time.Now()
 	conn := newConnection(s.Config, nconn)
 	conn.handle(ctx, s.serverConfig.get(ctx), func(sconn *ssh.ServerConn, channel ssh.Channel, requests <-chan *ssh.Request) error {
 		session := &session{
@@ -178,6 +179,7 @@
 			channel:     channel,
 			gitlabKeyId: sconn.Permissions.Extensions["key-id"],
 			remoteAddr:  remoteAddr,
+			started:     started,
 		}
 
 		return session.handle(ctx, requests)
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1653457980 0
#      Wed May 25 05:53:00 2022 +0000
# Node ID 74d8627ee0a7594386438c942c27f5f1e217b1fc
# Parent  07b38ad716c3a4de5f8d3c411704fa03e3710120
# Parent  e3ba7fb0b513299d331e32126b7a477fd6c867fc
Merge branch 'id-session-duration' into 'main'

Improve establish session duration metrics

See merge request gitlab-org/gitlab-shell!651

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -25,13 +25,11 @@
 var EOFTimeout = 10 * time.Second
 
 type connection struct {
-	cfg                      *config.Config
-	concurrentSessions       *semaphore.Weighted
-	nconn                    net.Conn
-	maxSessions              int64
-	remoteAddr               string
-	started                  time.Time
-	establishSessionDuration float64
+	cfg                *config.Config
+	concurrentSessions *semaphore.Weighted
+	nconn              net.Conn
+	maxSessions        int64
+	remoteAddr         string
 }
 
 type channelHandler func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error
@@ -45,7 +43,6 @@
 		concurrentSessions: semaphore.NewWeighted(maxSessions),
 		nconn:              nconn,
 		remoteAddr:         nconn.RemoteAddr().String(),
-		started:            time.Now(),
 	}
 }
 
@@ -64,11 +61,7 @@
 	c.handleRequests(ctx, sconn, chans, handler)
 
 	reason := sconn.Wait()
-	log.WithContextFields(ctx, log.Fields{
-		"duration_s":                   time.Since(c.started).Seconds(),
-		"establish_session_duration_s": c.establishSessionDuration,
-		"reason":                       reason,
-	}).Info("server: handleConn: done")
+	log.WithContextFields(ctx, log.Fields{"reason": reason}).Info("server: handleConn: done")
 }
 
 func (c *connection) initServerConn(ctx context.Context, srvCfg *ssh.ServerConfig) (*ssh.ServerConn, <-chan ssh.NewChannel, error) {
@@ -120,10 +113,10 @@
 
 		go func() {
 			defer func(started time.Time) {
-				metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
+				duration := time.Since(started).Seconds()
+				metrics.SshdSessionDuration.Observe(duration)
+				ctxlog.WithFields(log.Fields{"duration_s": duration}).Info("connection: handleRequests: done")
 			}(time.Now())
-			c.establishSessionDuration = time.Since(c.started).Seconds()
-			metrics.SshdSessionEstablishedDuration.Observe(c.establishSessionDuration)
 
 			defer c.concurrentSessions.Release(1)
 
@@ -139,8 +132,6 @@
 			if err != nil {
 				c.trackError(ctxlog, err)
 			}
-
-			ctxlog.Info("connection: handleRequests: done")
 		}()
 	}
 
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -5,6 +5,7 @@
 	"errors"
 	"fmt"
 	"reflect"
+	"time"
 
 	"gitlab.com/gitlab-org/labkit/log"
 	"golang.org/x/crypto/ssh"
@@ -16,6 +17,7 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
@@ -29,6 +31,7 @@
 	// State managed by the session
 	execCmd            string
 	gitProtocolVersion string
+	started            time.Time
 }
 
 type execRequest struct {
@@ -171,7 +174,12 @@
 	}
 
 	cmdName := reflect.TypeOf(cmd).String()
-	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("session: handleShell: executing command")
+
+	establishSessionDuration := time.Since(s.started).Seconds()
+	ctxlog.WithFields(log.Fields{
+		"env": env, "command": cmdName, "established_session_duration_s": establishSessionDuration,
+	}).Info("session: handleShell: executing command")
+	metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
 
 	if err := cmd.Execute(ctx); err != nil {
 		grpcStatus := grpcstatus.Convert(err)
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -171,6 +171,7 @@
 		}
 	}()
 
+	started := time.Now()
 	conn := newConnection(s.Config, nconn)
 	conn.handle(ctx, s.serverConfig.get(ctx), func(sconn *ssh.ServerConn, channel ssh.Channel, requests <-chan *ssh.Request) error {
 		session := &session{
@@ -178,6 +179,7 @@
 			channel:     channel,
 			gitlabKeyId: sconn.Permissions.Extensions["key-id"],
 			remoteAddr:  remoteAddr,
+			started:     started,
 		}
 
 		return session.handle(ctx, requests)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653458300 -14400
#      Wed May 25 09:58:20 2022 +0400
# Node ID 8946ad1922582f61ba1733dacb96488c4dcb1ed5
# Parent  74d8627ee0a7594386438c942c27f5f1e217b1fc
Calculate session start after the connection is established

diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -171,7 +171,6 @@
 		}
 	}()
 
-	started := time.Now()
 	conn := newConnection(s.Config, nconn)
 	conn.handle(ctx, s.serverConfig.get(ctx), func(sconn *ssh.ServerConn, channel ssh.Channel, requests <-chan *ssh.Request) error {
 		session := &session{
@@ -179,7 +178,7 @@
 			channel:     channel,
 			gitlabKeyId: sconn.Permissions.Extensions["key-id"],
 			remoteAddr:  remoteAddr,
-			started:     started,
+			started:     time.Now(),
 		}
 
 		return session.handle(ctx, requests)
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1653460729 0
#      Wed May 25 06:38:49 2022 +0000
# Node ID 4984af77b5c3df66b168e5c0087f4cd60f615561
# Parent  74d8627ee0a7594386438c942c27f5f1e217b1fc
# Parent  8946ad1922582f61ba1733dacb96488c4dcb1ed5
Merge branch 'id-calculate-started-just-before-session-handling' into 'main'

Calculate session start after the connection is established

See merge request gitlab-org/gitlab-shell!653

diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -171,7 +171,6 @@
 		}
 	}()
 
-	started := time.Now()
 	conn := newConnection(s.Config, nconn)
 	conn.handle(ctx, s.serverConfig.get(ctx), func(sconn *ssh.ServerConn, channel ssh.Channel, requests <-chan *ssh.Request) error {
 		session := &session{
@@ -179,7 +178,7 @@
 			channel:     channel,
 			gitlabKeyId: sconn.Permissions.Extensions["key-id"],
 			remoteAddr:  remoteAddr,
-			started:     started,
+			started:     time.Now(),
 		}
 
 		return session.handle(ctx, requests)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653458005 -14400
#      Wed May 25 09:53:25 2022 +0400
# Node ID b602f160745449f796a881587617e23563ccbf10
# Parent  4984af77b5c3df66b168e5c0087f4cd60f615561
Release 14.7.1

- Log gitlab-sshd session level indicator errors !650
- Improve establish session duration metrics !651

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v14.7.1
+
+- Log gitlab-sshd session level indicator errors !650
+- Improve establish session duration metrics !651
+
 v14.7.0
 
 - Abort long-running unauthenticated SSH connections !647
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.7.0
+14.7.1
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1653461319 0
#      Wed May 25 06:48:39 2022 +0000
# Node ID 4d1b81301352f36de1c3c11f5a68dd5cf48afab4
# Parent  4984af77b5c3df66b168e5c0087f4cd60f615561
# Parent  b602f160745449f796a881587617e23563ccbf10
Merge branch 'id-release-14-7-1' into 'main'

Release 14.7.1

See merge request gitlab-org/gitlab-shell!652

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v14.7.1
+
+- Log gitlab-sshd session level indicator errors !650
+- Improve establish session duration metrics !651
+
 v14.7.0
 
 - Abort long-running unauthenticated SSH connections !647
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.7.0
+14.7.1
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1654057997 -14400
#      Wed Jun 01 08:33:17 2022 +0400
# Node ID 5ee2cc7cd7fbb191a859820e0e90243755abf41f
# Parent  4d1b81301352f36de1c3c11f5a68dd5cf48afab4
Exclude disallowed command from error rate

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -14,6 +14,7 @@
 	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
@@ -165,6 +166,10 @@
 		return
 	}
 
+	if errors.Is(err, disallowedcommand.Error) {
+		return
+	}
+
 	grpcCode := grpcstatus.Code(err)
 	if grpcCode == grpccodes.Canceled || grpcCode == grpccodes.Unavailable {
 		return
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -15,6 +15,7 @@
 	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
@@ -212,30 +213,24 @@
 	require.InDelta(t, initialSessionsTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 
-	conn, chans = setup(1, newChannel)
-	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
-		close(chans)
-		return grpcstatus.Error(grpccodes.Canceled, "canceled")
-	})
-
-	require.InDelta(t, initialSessionsTotal+2, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
-	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+	for i, ignoredError := range []struct {
+		desc string
+		err  error
+	}{
+		{"canceled requests", grpcstatus.Error(grpccodes.Canceled, "canceled")},
+		{"unavailable Gitaly", grpcstatus.Error(grpccodes.Unavailable, "unavailable")},
+		{"api error", &client.ApiError{"api error"}},
+		{"disallowed command", disallowedcommand.Error},
+	} {
+		t.Run(ignoredError.desc, func(t *testing.T) {
+			conn, chans = setup(1, newChannel)
+			conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
+				close(chans)
+				return ignoredError.err
+			})
 
-	conn, chans = setup(1, newChannel)
-	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
-		close(chans)
-		return &client.ApiError{"api error"}
-	})
-
-	require.InDelta(t, initialSessionsTotal+3, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
-	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
-
-	conn, chans = setup(1, newChannel)
-	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
-		close(chans)
-		return grpcstatus.Error(grpccodes.Unavailable, "unavailable")
-	})
-
-	require.InDelta(t, initialSessionsTotal+4, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
-	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+			require.InDelta(t, initialSessionsTotal+2+float64(i), testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+			require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+		})
+	}
 }
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1654491072 0
#      Mon Jun 06 04:51:12 2022 +0000
# Node ID 6176a3759da53856c0f46f41d3179f503a7ccea7
# Parent  4d1b81301352f36de1c3c11f5a68dd5cf48afab4
# Parent  5ee2cc7cd7fbb191a859820e0e90243755abf41f
Merge branch 'id-ignore-disallowed-cmd-err' into 'main'

Exclude disallowed command from error rate

See merge request gitlab-org/gitlab-shell!654

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -14,6 +14,7 @@
 	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
@@ -165,6 +166,10 @@
 		return
 	}
 
+	if errors.Is(err, disallowedcommand.Error) {
+		return
+	}
+
 	grpcCode := grpcstatus.Code(err)
 	if grpcCode == grpccodes.Canceled || grpcCode == grpccodes.Unavailable {
 		return
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -15,6 +15,7 @@
 	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
@@ -212,30 +213,24 @@
 	require.InDelta(t, initialSessionsTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 
-	conn, chans = setup(1, newChannel)
-	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
-		close(chans)
-		return grpcstatus.Error(grpccodes.Canceled, "canceled")
-	})
-
-	require.InDelta(t, initialSessionsTotal+2, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
-	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+	for i, ignoredError := range []struct {
+		desc string
+		err  error
+	}{
+		{"canceled requests", grpcstatus.Error(grpccodes.Canceled, "canceled")},
+		{"unavailable Gitaly", grpcstatus.Error(grpccodes.Unavailable, "unavailable")},
+		{"api error", &client.ApiError{"api error"}},
+		{"disallowed command", disallowedcommand.Error},
+	} {
+		t.Run(ignoredError.desc, func(t *testing.T) {
+			conn, chans = setup(1, newChannel)
+			conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
+				close(chans)
+				return ignoredError.err
+			})
 
-	conn, chans = setup(1, newChannel)
-	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
-		close(chans)
-		return &client.ApiError{"api error"}
-	})
-
-	require.InDelta(t, initialSessionsTotal+3, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
-	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
-
-	conn, chans = setup(1, newChannel)
-	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
-		close(chans)
-		return grpcstatus.Error(grpccodes.Unavailable, "unavailable")
-	})
-
-	require.InDelta(t, initialSessionsTotal+4, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
-	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+			require.InDelta(t, initialSessionsTotal+2+float64(i), testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+			require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+		})
+	}
 }
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1654504723 -14400
#      Mon Jun 06 12:38:43 2022 +0400
# Node ID 7d60e21882ecf8a085f61c540ba5fbdfb159b130
# Parent  6176a3759da53856c0f46f41d3179f503a7ccea7
Release 14.7.2

- Exclude disallowed command from error rate

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.7.2
+
+- Exclude disallowed command from error rate !654
+
 v14.7.1
 
 - Log gitlab-sshd session level indicator errors !650
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.7.1
+14.7.2
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1654505161 0
#      Mon Jun 06 08:46:01 2022 +0000
# Node ID 02577e13cefa3ad7eda5a8f640694cd857246a8d
# Parent  6176a3759da53856c0f46f41d3179f503a7ccea7
# Parent  7d60e21882ecf8a085f61c540ba5fbdfb159b130
Merge branch 'id-release-14-7-2' into 'main'

Release 14.7.2

See merge request gitlab-org/gitlab-shell!655

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.7.2
+
+- Exclude disallowed command from error rate !654
+
 v14.7.1
 
 - Log gitlab-sshd session level indicator errors !650
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.7.1
+14.7.2
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1654535340 25200
#      Mon Jun 06 10:09:00 2022 -0700
# Node ID c592da0f9a7e25a4213aa564acbdad38c1fd6acf
# Parent  02577e13cefa3ad7eda5a8f640694cd857246a8d
Ignore "not our ref" errors from gitlab-sshd error metrics

If a client requests a ref that cannot be found in the repository,
previously gitlab-sshd would record it as part of its service level
indicator metric. This is really an application error between the
client and the Git repository, so we exclude it from our metrics.

Relates to
https://gitlab.com/gitlab-com/gl-infra/reliability/-/issues/15848

Changelog: fixed

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -21,7 +21,10 @@
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
-const KeepAliveMsg = "keepalive@openssh.com"
+const (
+	KeepAliveMsg   = "keepalive@openssh.com"
+	NotOurRefError = `exit status 128, stderr: "fatal: git upload-pack: not our ref `
+)
 
 var EOFTimeout = 10 * time.Second
 
@@ -173,6 +176,8 @@
 	grpcCode := grpcstatus.Code(err)
 	if grpcCode == grpccodes.Canceled || grpcCode == grpccodes.Unavailable {
 		return
+	} else if grpcCode == grpccodes.Internal && strings.Contains(err.Error(), NotOurRefError) {
+		return
 	}
 
 	metrics.SliSshdSessionsErrorsTotal.Inc()
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -221,6 +221,7 @@
 		{"unavailable Gitaly", grpcstatus.Error(grpccodes.Unavailable, "unavailable")},
 		{"api error", &client.ApiError{"api error"}},
 		{"disallowed command", disallowedcommand.Error},
+		{"not our ref", grpcstatus.Error(grpccodes.Internal, `rpc error: code = Internal desc = cmd wait: exit status 128, stderr: "fatal: git upload-pack: not our ref 9106d18f6a1b8022f6517f479696f3e3ea5e68c1"`)},
 	} {
 		t.Run(ignoredError.desc, func(t *testing.T) {
 			conn, chans = setup(1, newChannel)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1654537263 0
#      Mon Jun 06 17:41:03 2022 +0000
# Node ID 2045af844a92d01d7feb031315da05df45186949
# Parent  02577e13cefa3ad7eda5a8f640694cd857246a8d
# Parent  c592da0f9a7e25a4213aa564acbdad38c1fd6acf
Merge branch 'sh-ignore-not-our-ref-errors' into 'main'

Ignore "not our ref" errors from gitlab-sshd error metrics

See merge request gitlab-org/gitlab-shell!656

diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -21,7 +21,10 @@
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
-const KeepAliveMsg = "keepalive@openssh.com"
+const (
+	KeepAliveMsg   = "keepalive@openssh.com"
+	NotOurRefError = `exit status 128, stderr: "fatal: git upload-pack: not our ref `
+)
 
 var EOFTimeout = 10 * time.Second
 
@@ -173,6 +176,8 @@
 	grpcCode := grpcstatus.Code(err)
 	if grpcCode == grpccodes.Canceled || grpcCode == grpccodes.Unavailable {
 		return
+	} else if grpcCode == grpccodes.Internal && strings.Contains(err.Error(), NotOurRefError) {
+		return
 	}
 
 	metrics.SliSshdSessionsErrorsTotal.Inc()
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -221,6 +221,7 @@
 		{"unavailable Gitaly", grpcstatus.Error(grpccodes.Unavailable, "unavailable")},
 		{"api error", &client.ApiError{"api error"}},
 		{"disallowed command", disallowedcommand.Error},
+		{"not our ref", grpcstatus.Error(grpccodes.Internal, `rpc error: code = Internal desc = cmd wait: exit status 128, stderr: "fatal: git upload-pack: not our ref 9106d18f6a1b8022f6517f479696f3e3ea5e68c1"`)},
 	} {
 		t.Run(ignoredError.desc, func(t *testing.T) {
 			conn, chans = setup(1, newChannel)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1654537499 -14400
#      Mon Jun 06 21:44:59 2022 +0400
# Node ID a2a739e3f554ecf529f321a4d7b32d178f578be7
# Parent  2045af844a92d01d7feb031315da05df45186949
Release v14.7.3

- Ignore "not our ref" errors from gitlab-sshd error metrics

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.7.3
+
+- Ignore "not our ref" errors from gitlab-sshd error metrics !656
+
 v14.7.2
 
 - Exclude disallowed command from error rate !654
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.7.2
+14.7.3
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1654538412 0
#      Mon Jun 06 18:00:12 2022 +0000
# Node ID caa03cf8351fe1ec3bc901200e04d13123011ac0
# Parent  2045af844a92d01d7feb031315da05df45186949
# Parent  a2a739e3f554ecf529f321a4d7b32d178f578be7
Merge branch 'id-release-14-7-3' into 'main'

Release v14.7.3

See merge request gitlab-org/gitlab-shell!657

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.7.3
+
+- Ignore "not our ref" errors from gitlab-sshd error metrics !656
+
 v14.7.2
 
 - Exclude disallowed command from error rate !654
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.7.2
+14.7.3
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1654590701 25200
#      Tue Jun 07 01:31:41 2022 -0700
# Node ID 815d4e96d450647f3d7ac1e08ebe19e44c57f172
# Parent  02577e13cefa3ad7eda5a8f640694cd857246a8d
Upgrade Gemfile.lock to use bundler to v2.3.15

This is just to minimize the versions of bundler used for development.
The GDK runs `support/bundle-install` in this directory to obtain the
version of bundler needed.

This relates to https://gitlab.com/gitlab-org/gitlab/-/issues/364373.

diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -113,4 +113,4 @@
   rspec (~> 3.8.0)
 
 BUNDLED WITH
-   2.3.6
+   2.3.15
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1654600872 0
#      Tue Jun 07 11:21:12 2022 +0000
# Node ID 4493ab240bbec0f36c1379d187f9f82e09f58f83
# Parent  caa03cf8351fe1ec3bc901200e04d13123011ac0
# Parent  815d4e96d450647f3d7ac1e08ebe19e44c57f172
Merge branch 'sh-upgrade-bundler-version' into 'main'

Upgrade Gemfile.lock to use bundler to v2.3.15

See merge request gitlab-org/gitlab-shell!658

diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -113,4 +113,4 @@
   rspec (~> 3.8.0)
 
 BUNDLED WITH
-   2.3.6
+   2.3.15
# HG changeset patch
# User Alejandro Rodríguez <alejorro70@gmail.com>
# Date 1654869688 -7200
#      Fri Jun 10 16:01:28 2022 +0200
# Node ID 20c30f8a78288a1a9f505105757d86297b21f193
# Parent  4493ab240bbec0f36c1379d187f9f82e09f58f83
Set BUNDLE_FROZEN to true

To follow rubygems' security adisory
https://github.com/rubygems/rubygems.org/security/advisories/GHSA-hccv-rwq6-vh79:

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,6 +9,7 @@
 
 variables:
   DOCKER_VERSION: "20.10.15"
+  BUNDLE_FROZEN: "true"
 
 workflow:
   rules: &workflow_rules
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1655196892 0
#      Tue Jun 14 08:54:52 2022 +0000
# Node ID d4ceca3bc914146fc28c8b239f2ea96eaa31a1b1
# Parent  4493ab240bbec0f36c1379d187f9f82e09f58f83
# Parent  20c30f8a78288a1a9f505105757d86297b21f193
Merge branch 'freeze-bundle' into 'main'

Set BUNDLE_FROZEN to true

Closes #562

See merge request gitlab-org/gitlab-shell!659

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,6 +9,7 @@
 
 variables:
   DOCKER_VERSION: "20.10.15"
+  BUNDLE_FROZEN: "true"
 
 workflow:
   rules: &workflow_rules
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1655248838 25200
#      Tue Jun 14 16:20:38 2022 -0700
# Node ID 613c8ffa93c6dc492503e2ebd1cc7b500059dcf4
# Parent  d4ceca3bc914146fc28c8b239f2ea96eaa31a1b1
gitlab-sshd: Update crypto module to fix RSA keys with old gpg-agent

When we put gitlab-sshd in production, we noticed a number of clients
using RSA keys would fail to login. The server would report:

```
ssh: signature "ssh-rsa" not compatible with selected algorithm "rsa-sha2-512"
```

This is reproducible on Ubuntu 18.04, which ships gpg-agent v2.2.4 and
OpenSSH v7.6. That version of gpg-agent does not support
`rsa-sha2-256` or `rsa-sha2-512`, but OpenSSH does. As a result,
OpenSSH specifies `rsa-sha-512` as the public key algorithm to use in
the user authentication request message, but gpg-agent includes an
`ssh-rsa` signature. OpenSSH servers tolerates this discrepancy, but
the Go implementation fails because it expects a strict match.

This commit pulls in
https://gitlab.com/gitlab-org/golang-crypto/-/merge_requests/9 to fix
the problem.

Relates to:

1. https://github.com/golang/go/issues/53391
2. https://gitlab.com/gitlab-org/gitlab-shell/-/issues/587

Changelog: fixed

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -81,4 +81,4 @@
 	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
 )
 
-replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac
+replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -888,8 +888,8 @@
 gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
-gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac h1:qNUzqBTbEGGjF5Fp0NWz3rNmqamwchxM+QKUZYeOS1c=
-gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed h1:aXSyBpG6K/QsTGevZnpFoDR7Nwvn24RpkDoWe37B8eY=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1655363387 0
#      Thu Jun 16 07:09:47 2022 +0000
# Node ID 0a35bc7598fe9d46f27ce99bb9641f29e7772ab6
# Parent  d4ceca3bc914146fc28c8b239f2ea96eaa31a1b1
# Parent  613c8ffa93c6dc492503e2ebd1cc7b500059dcf4
Merge branch 'sh-update-crypto-lib' into 'main'

gitlab-sshd: Update crypto module to fix RSA keys with old gpg-agent

See merge request gitlab-org/gitlab-shell!662

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -81,4 +81,4 @@
 	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
 )
 
-replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac
+replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -888,8 +888,8 @@
 gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
-gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac h1:qNUzqBTbEGGjF5Fp0NWz3rNmqamwchxM+QKUZYeOS1c=
-gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed h1:aXSyBpG6K/QsTGevZnpFoDR7Nwvn24RpkDoWe37B8eY=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1655363436 -7200
#      Thu Jun 16 09:10:36 2022 +0200
# Node ID c310139f96439ddba4192f3d2839d7f7c9609626
# Parent  0a35bc7598fe9d46f27ce99bb9641f29e7772ab6
Release v14.7.4

- Update crypto module to fix RSA keys with old gpg-agent

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.7.4
+
+- gitlab-sshd: Update crypto module to fix RSA keys with old gpg-agent !662
+
 v14.7.3
 
 - Ignore "not our ref" errors from gitlab-sshd error metrics !656
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.7.3
+14.7.4
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1655364199 0
#      Thu Jun 16 07:23:19 2022 +0000
# Node ID d27c5af605da53832cae29fa0f9a200c1447047b
# Parent  0a35bc7598fe9d46f27ce99bb9641f29e7772ab6
# Parent  c310139f96439ddba4192f3d2839d7f7c9609626
Merge branch 'id-release-14-7-4' into 'main'

Release v14.7.4

See merge request gitlab-org/gitlab-shell!663

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.7.4
+
+- gitlab-sshd: Update crypto module to fix RSA keys with old gpg-agent !662
+
 v14.7.3
 
 - Ignore "not our ref" errors from gitlab-sshd error metrics !656
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.7.3
+14.7.4
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1656001706 25200
#      Thu Jun 23 09:28:26 2022 -0700
# Node ID d65758fc2f6f6035f0fafbfe537bfb6c3676a785
# Parent  d27c5af605da53832cae29fa0f9a200c1447047b
Fix make install copying the wrong binaries

While testing
https://gitlab.com/gitlab-org/build/CNG/-/merge_requests/1062, we
found `make install` was not copying the right binaries, such as
`gitlab-shell-authorized-keys-check`.

This might have originally been written with a single binary in mind
(https://gitlab.com/gitlab-org/gitlab-shell/-/issues/207).

Changelog: fixed

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -66,7 +66,6 @@
 	mkdir -p $(DESTDIR)$(PREFIX)/bin/
 	install -m755 bin/check $(DESTDIR)$(PREFIX)/bin/check
 	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell
-	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-keys-check
-	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-principals-check
-	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-sshd
-
+	install -m755 bin/gitlab-shell-authorized-keys-check $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-keys-check
+	install -m755 bin/gitlab-shell-authorized-principals-check $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-principals-check
+	install -m755 bin/gitlab-sshd $(DESTDIR)$(PREFIX)/bin/gitlab-sshd
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1656002841 0
#      Thu Jun 23 16:47:21 2022 +0000
# Node ID 0093de29bc5020319e5d6ef4a9be0f4b09b46a1e
# Parent  d27c5af605da53832cae29fa0f9a200c1447047b
# Parent  d65758fc2f6f6035f0fafbfe537bfb6c3676a785
Merge branch 'fix-make-install' into 'main'

Fix make install copying the wrong binaries

See merge request gitlab-org/gitlab-shell!664

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -66,7 +66,6 @@
 	mkdir -p $(DESTDIR)$(PREFIX)/bin/
 	install -m755 bin/check $(DESTDIR)$(PREFIX)/bin/check
 	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell
-	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-keys-check
-	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-principals-check
-	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-sshd
-
+	install -m755 bin/gitlab-shell-authorized-keys-check $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-keys-check
+	install -m755 bin/gitlab-shell-authorized-principals-check $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-principals-check
+	install -m755 bin/gitlab-sshd $(DESTDIR)$(PREFIX)/bin/gitlab-sshd
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1654983745 25200
#      Sat Jun 11 14:42:25 2022 -0700
# Node ID c1924280df1446887110459c5ad1aeb05f9efb1b
# Parent  0093de29bc5020319e5d6ef4a9be0f4b09b46a1e
gitlab-sshd: Add support for configuring host certificates

This adds support for specifying host certificates via the
`host_cert_files` option and advertises the signed key to the
client. This acts similarly to OpenSSH's `HostCertificate` parameter:
gitlab-sshd attempts to match a host key to its certificate, and then
substitutes the matching host key with a certificate signed by a
trusted certificate authority's key.

This is the first requirement to supporting SSH certificates. This
will enable the client to trust the server if both trust a common
certificate authority. The `TrustedUserCAKeys` option will need to be
supported later for the server to trust all user keys signed by this
certificate authority.

Relates to https://gitlab.com/gitlab-org/gitlab-shell/-/issues/495

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -99,3 +99,7 @@
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
     - /run/secrets/ssh-hostkeys/ssh_host_ecdsa_key
     - /run/secrets/ssh-hostkeys/ssh_host_ed25519_key
+  host_key_certs:
+    - /run/secrets/ssh-hostkeys/ssh_host_rsa_key-cert.pub
+    - /run/secrets/ssh-hostkeys/ssh_host_ecdsa_key-cert.pub
+    - /run/secrets/ssh-hostkeys/ssh_host_ed25519_key-cert.pub
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -36,6 +36,7 @@
 	ReadinessProbe          string       `yaml:"readiness_probe"`
 	LivenessProbe           string       `yaml:"liveness_probe"`
 	HostKeyFiles            []string     `yaml:"host_key_files,omitempty"`
+	HostCertFiles           []string     `yaml:"host_cert_files,omitempty"`
 	MACs                    []string     `yaml:"macs"`
 	KexAlgorithms           []string     `yaml:"kex_algorithms"`
 	Ciphers                 []string     `yaml:"ciphers"`
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -39,17 +39,14 @@
 type serverConfig struct {
 	cfg                  *config.Config
 	hostKeys             []ssh.Signer
+	hostKeyToCertMap     map[string]*ssh.Certificate
 	authorizedKeysClient *authorizedkeys.Client
 }
 
-func newServerConfig(cfg *config.Config) (*serverConfig, error) {
-	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
-	if err != nil {
-		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
-	}
+func parseHostKeys(keyFiles []string) []ssh.Signer {
+	var hostKeys []ssh.Signer
 
-	var hostKeys []ssh.Signer
-	for _, filename := range cfg.Server.HostKeyFiles {
+	for _, filename := range keyFiles {
 		keyRaw, err := os.ReadFile(filename)
 		if err != nil {
 			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("Failed to read host key")
@@ -63,11 +60,70 @@
 
 		hostKeys = append(hostKeys, key)
 	}
+
+	return hostKeys
+}
+
+func parseHostCerts(hostKeys []ssh.Signer, certFiles []string) map[string]*ssh.Certificate {
+	keyToCertMap := map[string]*ssh.Certificate{}
+	hostKeyIndex := make(map[string]int)
+
+	for index, hostKey := range hostKeys {
+		hostKeyIndex[string(hostKey.PublicKey().Marshal())] = index
+	}
+
+	for _, filename := range certFiles {
+		keyRaw, err := os.ReadFile(filename)
+		if err != nil {
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("failed to read host certificate")
+			continue
+		}
+		publicKey, _, _, _, err := ssh.ParseAuthorizedKey(keyRaw)
+		if err != nil {
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("failed to parse host certificate")
+			continue
+		}
+
+		cert, ok := publicKey.(*ssh.Certificate)
+		if !ok {
+			log.WithFields(log.Fields{"filename": filename}).Warn("failed to decode host certificate")
+			continue
+		}
+
+		hostRawKey := string(cert.Key.Marshal())
+		index, found := hostKeyIndex[hostRawKey]
+		if found {
+			keyToCertMap[hostRawKey] = cert
+
+			certSigner, err := ssh.NewCertSigner(cert, hostKeys[index])
+			if err != nil {
+				log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("the host certificate doesn't match the host private key")
+				continue
+			}
+
+			hostKeys[index] = certSigner
+		} else {
+			log.WithFields(log.Fields{"filename": filename}).Warnf("no matching private key for certificate %s", filename)
+		}
+	}
+
+	return keyToCertMap
+}
+
+func newServerConfig(cfg *config.Config) (*serverConfig, error) {
+	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+	}
+
+	hostKeys := parseHostKeys(cfg.Server.HostKeyFiles)
 	if len(hostKeys) == 0 {
 		return nil, fmt.Errorf("No host keys could be loaded, aborting")
 	}
 
-	return &serverConfig{cfg: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
+	hostKeyToCertMap := parseHostCerts(hostKeys, cfg.Server.HostCertFiles)
+
+	return &serverConfig{cfg: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys, hostKeyToCertMap: hostKeyToCertMap}, nil
 }
 
 func (s *serverConfig) getAuthKey(ctx context.Context, user string, key ssh.PublicKey) (*authorizedkeys.Response, error) {
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
--- a/internal/sshd/server_config_test.go
+++ b/internal/sshd/server_config_test.go
@@ -5,6 +5,7 @@
 	"crypto/dsa"
 	"crypto/rand"
 	"crypto/rsa"
+	"os"
 	"path"
 	"testing"
 
@@ -22,6 +23,45 @@
 	require.Equal(t, "No host keys could be loaded, aborting", err.Error())
 }
 
+func TestHostKeyAndCerts(t *testing.T) {
+	testhelper.PrepareTestRootDir(t)
+
+	srvCfg := config.ServerConfig{
+		Listen:                  "127.0.0.1",
+		ConcurrentSessionsLimit: 1,
+		HostKeyFiles: []string{
+			path.Join(testhelper.TestRoot, "certs/valid/server.key"),
+		},
+		HostCertFiles: []string{
+			path.Join(testhelper.TestRoot, "certs/valid/server-cert.pub"),
+			path.Join(testhelper.TestRoot, "certs/valid/server2-cert.pub"),
+			path.Join(testhelper.TestRoot, "certs/invalid/server-cert.pub"),
+			path.Join(testhelper.TestRoot, "certs/invalid-path.key"),
+			path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+		},
+	}
+
+	cfg, err := newServerConfig(
+		&config.Config{GitlabUrl: "http://localhost", User: "user", Server: srvCfg},
+	)
+	require.NoError(t, err)
+
+	require.Len(t, cfg.hostKeys, 1)
+	require.Len(t, cfg.hostKeyToCertMap, 1)
+
+	// Check that the entry is pointing to the server's public key
+	data, err := os.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server.pub"))
+	require.NoError(t, err)
+
+	publicKey, _, _, _, err := ssh.ParseAuthorizedKey(data)
+	require.NoError(t, err)
+	require.NotNil(t, publicKey)
+	cert, ok := cfg.hostKeyToCertMap[string(publicKey.Marshal())]
+	require.True(t, ok)
+	require.NotNil(t, cert)
+	require.Equal(t, cert, cfg.hostKeys[0].PublicKey())
+}
+
 func TestFailedAuthorizedKeysClient(t *testing.T) {
 	_, err := newServerConfig(&config.Config{GitlabUrl: "ftp://localhost"})
 
diff --git a/internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub b/internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub
@@ -0,0 +1,1 @@
+ssh-rsa-cert-v01@openssh.com AAAAB3NzaC1yc2EAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yql not_a_valid_cert.pub
diff --git a/internal/testhelper/testdata/testroot/certs/valid/ca b/internal/testhelper/testdata/testroot/certs/valid/ca
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/ca
@@ -0,0 +1,38 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAYEA3IUBN80/BJG5ut9U238FwIGAfMZQoOXYbFtiQZo+U+cdbGJlEGgV
++IToGN78YJypkbK8NiptMMOgWZ8eu6DCJMNxjPb7e3XYGrmHPeoK5Z9ioESDw0QSkxDIfw
+FoLSnx0+eiXS138ypENgLT/hOMl+fmroA74ZpvkvIpK+RoAo1qlWCWtA7NygkQkWRgnnAj
+4jkDKq9aT1iHGa9nQYLuTtfiX3l35/oOOMJFrDg7bb5EHnBsKewDoAxdqNbxp4W/sQyUF4
+NbOFLGzRavA6eNRfOx9UXcdy0v9+UgCmu92nbiPmNBo9wY3D9prwXAWetTM1B3ZCDk1hqI
+cXRg6pNBNC9e2Fubchs9TcrLaZpr4kgFvTekGcof1m2ozJTVlcIJlJpy95mp1uXwxTPbfX
+KGPvKu7NroR2avkAIfwRj5E/8HwrgDH2r/4uRPadRCx9hm38HI1n6S4YrrsmL9ffLW6SCf
+EG6c7+URDQr18Vq9YyxMmu09EK7VlDDX0JM0Uan5AAAFkA5fIlIOXyJSAAAAB3NzaC1yc2
+EAAAGBANyFATfNPwSRubrfVNt/BcCBgHzGUKDl2GxbYkGaPlPnHWxiZRBoFfiE6Bje/GCc
+qZGyvDYqbTDDoFmfHrugwiTDcYz2+3t12Bq5hz3qCuWfYqBEg8NEEpMQyH8BaC0p8dPnol
+0td/MqRDYC0/4TjJfn5q6AO+Gab5LyKSvkaAKNapVglrQOzcoJEJFkYJ5wI+I5AyqvWk9Y
+hxmvZ0GC7k7X4l95d+f6DjjCRaw4O22+RB5wbCnsA6AMXajW8aeFv7EMlBeDWzhSxs0Wrw
+OnjUXzsfVF3HctL/flIAprvdp24j5jQaPcGNw/aa8FwFnrUzNQd2Qg5NYaiHF0YOqTQTQv
+Xthbm3IbPU3Ky2maa+JIBb03pBnKH9ZtqMyU1ZXCCZSacveZqdbl8MUz231yhj7yruza6E
+dmr5ACH8EY+RP/B8K4Ax9q/+LkT2nUQsfYZt/ByNZ+kuGK67Ji/X3y1ukgnxBunO/lEQ0K
+9fFavWMsTJrtPRCu1ZQw19CTNFGp+QAAAAMBAAEAAAGACHnYRSPPc0aCpAsngNRODUss/B
+7HRJfxDKEqkqjyElmEyQCzL8FAbu/019fiTXhYEDCViWNyFPi/9hHmpYGVVMJqX+eyXNl3
+t/c/moKfbpoEuXJIuj2olRyFCFSug2XkVKfHlttDjAYo3waWzWJE+iXAuR5WruI3vacvK+
++4i7iRyzIOONeE02orx9ra19wplO1qEL7ysrANaVBToLH+pOspWVAa6sCywT2+XdM/fYVd
+qunZTncy4Hj5NJ8mZLEATfJKnT2v7C47fBjN+ylqpyTImBZxSfVyjrljcQXb9ExjAhVTjv
+tBuZdB1NPnok9cycwpg6aGXuZX2mSQWROhHM/r80kUzfxJpRDs/AqMWRZYC2k/kCKbXg7S
+1cuAwJ2SiH5jslekhbB8bCU3rL2SgUV4oZsqh5fb6ZsytXarbzX/8Kcmb4KGsjZ7wBD6Yu
+sJ05TkzC/HkOT3xTXwyzZpEldKucLClnY3Boq8pkO1EoUD8uPJNgSgukH9W5SleaIxAAAA
+wEzXR8Av4SxsgWW2pPVtQaeKhUKrid21RuPF5/7c4PZ4GlnhL3Fod4PwdD8yPjuIuI7s6/
+9HRxzi+vr616/BXMagGWPSZEQMaX6I/L5CSratN2Dk1jSYcH501GseILr+kIcZhe7HoEf2
+xbr8ByF88DXpeSdimIqMeVYTPGWac7oSf3Y5WHi9FUuJ4BEccu8bLIXWkGMK6yi/zJo1RQ
+u4aMzdMyzat0C2aeAm40HABdUv350K/H20Voj7zfhmlXvQ7wAAAMEA8a1oEPFL1+cAqfUD
+Jbx+KWyw/1kFBIpU93rk2qfJR593nMLebAk0l9qfhbvlN6GTNcGETjzGK1bHaD9G14c2IT
+bFcIoKmah6LygIlMGwdTMSWPPrczeIhMy6H0rJ2lDa208+nLwKqlFlMDYNpycL2Q1ZynnB
+fYqfRiUSDJcs+2jfTX0gA17NuSwqp6j/JlMm45tN3GK1neIVH+4PBazBXqZTzdfCfqJ9r5
+TWJw2i6CsSlCDAtO3uo+Pyj327RbNtAAAAwQDplpqK2+0+QiQB+LUeT0hfWp5/HmXJjfgm
+u+xIICJKPqOYwDvBWEHHssv7YS70dFJrbENJ66NZfdv+foDbQXrr10odbIntk9QoO4CS1g
+zd63kolFCLhbwkYos45CjJIPuzNDeiYIgsLEOQwnjHbp3HxAIywxtUPKj80YmfoogeidiD
+JNMwRoJfqlNziW1PDq0r8Zhw2lbyGZPI218ox7tsJ94BS4MFJfgASwO9qcDsaYz23sS8uQ
+BBbY6cCknC7T0AAAAUc3Rhbmh1QGpldC1hcm0ubG9jYWwBAgMEBQYH
+-----END OPENSSH PRIVATE KEY-----
diff --git a/internal/testhelper/testdata/testroot/certs/valid/ca.pub b/internal/testhelper/testdata/testroot/certs/valid/ca.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/ca.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDchQE3zT8Ekbm631TbfwXAgYB8xlCg5dhsW2JBmj5T5x1sYmUQaBX4hOgY3vxgnKmRsrw2Km0ww6BZnx67oMIkw3GM9vt7ddgauYc96grln2KgRIPDRBKTEMh/AWgtKfHT56JdLXfzKkQ2AtP+E4yX5+augDvhmm+S8ikr5GgCjWqVYJa0Ds3KCRCRZGCecCPiOQMqr1pPWIcZr2dBgu5O1+JfeXfn+g44wkWsODttvkQecGwp7AOgDF2o1vGnhb+xDJQXg1s4UsbNFq8Dp41F87H1Rdx3LS/35SAKa73aduI+Y0Gj3BjcP2mvBcBZ61MzUHdkIOTWGohxdGDqk0E0L17YW5tyGz1NystpmmviSAW9N6QZyh/WbajMlNWVwgmUmnL3manW5fDFM9t9coY+8q7s2uhHZq+QAh/BGPkT/wfCuAMfav/i5E9p1ELH2GbfwcjWfpLhiuuyYv198tbpIJ8Qbpzv5RENCvXxWr1jLEya7T0QrtWUMNfQkzRRqfk= test@test.example.org
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server-cert.pub b/internal/testhelper/testdata/testroot/certs/valid/server-cert.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server-cert.pub
@@ -0,0 +1,1 @@
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgNGUtD+qw0Xj5NU2uj4+4LoCWPcvXP54F9Adw/hWN5LAAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yqlAAAAAAAAAAAAAAACAAAABnNlcnZlcgAAAAAAAAAAAAAAAP//////////AAAAAAAAAAAAAAAAAAABlwAAAAdzc2gtcnNhAAAAAwEAAQAAAYEA3IUBN80/BJG5ut9U238FwIGAfMZQoOXYbFtiQZo+U+cdbGJlEGgV+IToGN78YJypkbK8NiptMMOgWZ8eu6DCJMNxjPb7e3XYGrmHPeoK5Z9ioESDw0QSkxDIfwFoLSnx0+eiXS138ypENgLT/hOMl+fmroA74ZpvkvIpK+RoAo1qlWCWtA7NygkQkWRgnnAj4jkDKq9aT1iHGa9nQYLuTtfiX3l35/oOOMJFrDg7bb5EHnBsKewDoAxdqNbxp4W/sQyUF4NbOFLGzRavA6eNRfOx9UXcdy0v9+UgCmu92nbiPmNBo9wY3D9prwXAWetTM1B3ZCDk1hqIcXRg6pNBNC9e2Fubchs9TcrLaZpr4kgFvTekGcof1m2ozJTVlcIJlJpy95mp1uXwxTPbfXKGPvKu7NroR2avkAIfwRj5E/8HwrgDH2r/4uRPadRCx9hm38HI1n6S4YrrsmL9ffLW6SCfEG6c7+URDQr18Vq9YyxMmu09EK7VlDDX0JM0Uan5AAABlAAAAAxyc2Etc2hhMi01MTIAAAGAapK5VzLDoRe88tnLORrH8VxLegTWPKGdn0k5Ye8tS5XUgd0N98gU669y4ErDNf0kPxlz40bjsfisEEtJ/N7m14IskCepScfZRh8w6QgPxbTOhmrc89xooqBUE50y5FU9hIIbzUrnEP+Dfu4IiFPToAguCa+KoAKiOX7lBQqEugV6uOWVZ2erPopEv+OiMLD8hXsuKKjQ+TomHZ/IjuXsFdXH7Vcl0VsPaAcxyCtDU7nCTJSTUoUZtMtwwpulp0e/zNmFrEn2Binz4jRaUlk3FM2fdbotviDQYeOY3npmxaWUvvQ/eKn0/DzUTAKAGr2LDa2XWnPhj51BS1XkNaUlnupdYmZ2Sok0R4U3bfVwokteREvAltGbXQSDtZwLS5NEY6vIdDrxpn5QRf9vGjqnc7piXxye9gcLne4YDUi24IhGyHrnWKCC0HjF7tuUhCOVKrqRdmHxRGWX3PlS8Xn6HHEPWU+YZnfT1V2W7LAFcDMozQbs4GPGzZdR3f3vCOJ8 server.pub
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server.pub b/internal/testhelper/testdata/testroot/certs/valid/server.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yql
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub b/internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub
@@ -0,0 +1,1 @@
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg3XO30wn4+lFxc5fLsanH4Pvu0OuKGHoR94zZjxx7qAEAAAADAQABAAABgQDK4MvpmiZ0cLS4+p3YEwcCwo4kVbJPTEeiIMuoEI7KJ3yqAjKJuns6VpnC6aRgu3LmuM4uBHcimQi415ClBOm4+tFiVsVNcAksA+QuU8rTEC6xPs96y1Y/qC+/WQn6+uKp1+fAspWsLpig3VXSTfq+YAcxZfTdO/6ck0kHVvxX096Ye7D0mdq2lWbGwSBlAbzU1wX/Znv5hLZD4DSG1cIjA9vQ/fJ9pwclZiS0qQ1VXdoIUAvL9tTAKj295VGT2NMGGYZQAQ0vXtM1YHOMAZ4XoPL1oVFjVEZoLRD58a6Dpe4hY8QKe6X1w7zU1rr5vVYrz7MTUa5pzsUSOeUTc3kyKJrExVkoLyBgYQq8qW9kYk/Ox9zHwTAKw9vwiIs2Mwe6nlxJ7cw4zykMsxPK4z9HkbZoTFWy3phuPFqs/WQoCvHTKNjWPab7UAameM6Dn0N83BF21tCTMWjkRCvtZVGIGZ7Y4cAiiN0OPzQWDasJ/IKVDRguZJRn3kgHCMYCgokAAAAAAAAAAAAAAAIAAAAHc2VydmVyMgAAAAAAAAAAAAAAAP//////////AAAAAAAAAAAAAAAAAAABlwAAAAdzc2gtcnNhAAAAAwEAAQAAAYEA3IUBN80/BJG5ut9U238FwIGAfMZQoOXYbFtiQZo+U+cdbGJlEGgV+IToGN78YJypkbK8NiptMMOgWZ8eu6DCJMNxjPb7e3XYGrmHPeoK5Z9ioESDw0QSkxDIfwFoLSnx0+eiXS138ypENgLT/hOMl+fmroA74ZpvkvIpK+RoAo1qlWCWtA7NygkQkWRgnnAj4jkDKq9aT1iHGa9nQYLuTtfiX3l35/oOOMJFrDg7bb5EHnBsKewDoAxdqNbxp4W/sQyUF4NbOFLGzRavA6eNRfOx9UXcdy0v9+UgCmu92nbiPmNBo9wY3D9prwXAWetTM1B3ZCDk1hqIcXRg6pNBNC9e2Fubchs9TcrLaZpr4kgFvTekGcof1m2ozJTVlcIJlJpy95mp1uXwxTPbfXKGPvKu7NroR2avkAIfwRj5E/8HwrgDH2r/4uRPadRCx9hm38HI1n6S4YrrsmL9ffLW6SCfEG6c7+URDQr18Vq9YyxMmu09EK7VlDDX0JM0Uan5AAABlAAAAAxyc2Etc2hhMi01MTIAAAGANOKG8Tq7kp9B5+CQEyb+mEatJOoQRV+4rpemWlfEw6TuVwQN2wSXc6XKBHzSG4NRnFkwk6GgiPLQEf4lBBKA8VYQnDKuhrHJlU4DFCRPw/aceHfCwNOruyJmuf91W3yEO/kYAd6EhkQiW/K3ky7BuXCqR34T2fBZSCeYhNcXWxhEMLoAuj0kEdX+YMNBmiPtinPE13KMFGyIVBm/ojgSZa8j4WnhDcK0cWv0OSGTgJF6q3hENCWRz2E1HroKUiABOy5Nca6gPVAi4OTd7gwER8eh9MngVHYorAJ3N9HjUh640SbL3zCC8f/lqIztqsHY0u3olsQ0gLXpFain+430HeyJlmVlsDZgQKRb90Mm1viSCKvHGpmVDYMimE9y0DCQS1i0yRGF1uSIPtuQ0NCbhS/HPKsT3nYgGCEuoB8aGOu3aGB/tmUkYXW+pwXRKqw0f/zX088XWYWvA+AR4hmmr6DDMnf/4EHgJp3xHTEwOBHCVj69xvlOawBNlL2X0b2p server2.pub
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server2.key b/internal/testhelper/testdata/testroot/certs/valid/server2.key
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server2.key
@@ -0,0 +1,38 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAYEAyuDL6ZomdHC0uPqd2BMHAsKOJFWyT0xHoiDLqBCOyid8qgIyibp7
+OlaZwumkYLty5rjOLgR3IpkIuNeQpQTpuPrRYlbFTXAJLAPkLlPK0xAusT7PestWP6gvv1
+kJ+vriqdfnwLKVrC6YoN1V0k36vmAHMWX03Tv+nJNJB1b8V9PemHuw9JnatpVmxsEgZQG8
+1NcF/2Z7+YS2Q+A0htXCIwPb0P3yfacHJWYktKkNVV3aCFALy/bUwCo9veVRk9jTBhmGUA
+ENL17TNWBzjAGeF6Dy9aFRY1RGaC0Q+fGug6XuIWPECnul9cO81Na6+b1WK8+zE1Guac7F
+EjnlE3N5MiiaxMVZKC8gYGEKvKlvZGJPzsfcx8EwCsPb8IiLNjMHup5cSe3MOM8pDLMTyu
+M/R5G2aExVst6YbjxarP1kKArx0yjY1j2m+1AGpnjOg59DfNwRdtbQkzFo5EQr7WVRiBme
+2OHAIojdDj80Fg2rCfyClQ0YLmSUZ95IBwjGAoKJAAAFkBOZmjETmZoxAAAAB3NzaC1yc2
+EAAAGBAMrgy+maJnRwtLj6ndgTBwLCjiRVsk9MR6Igy6gQjsonfKoCMom6ezpWmcLppGC7
+cua4zi4EdyKZCLjXkKUE6bj60WJWxU1wCSwD5C5TytMQLrE+z3rLVj+oL79ZCfr64qnX58
+CylawumKDdVdJN+r5gBzFl9N07/pyTSQdW/FfT3ph7sPSZ2raVZsbBIGUBvNTXBf9me/mE
+tkPgNIbVwiMD29D98n2nByVmJLSpDVVd2ghQC8v21MAqPb3lUZPY0wYZhlABDS9e0zVgc4
+wBnheg8vWhUWNURmgtEPnxroOl7iFjxAp7pfXDvNTWuvm9VivPsxNRrmnOxRI55RNzeTIo
+msTFWSgvIGBhCrypb2RiT87H3MfBMArD2/CIizYzB7qeXEntzDjPKQyzE8rjP0eRtmhMVb
+LemG48Wqz9ZCgK8dMo2NY9pvtQBqZ4zoOfQ3zcEXbW0JMxaOREK+1lUYgZntjhwCKI3Q4/
+NBYNqwn8gpUNGC5klGfeSAcIxgKCiQAAAAMBAAEAAAGAUxojzMt85wNns8HMuD6LB6FkEh
+QcVwka6plecrhdlQb5tLXzt6DwayQgFcwYrhr6ZPHcWtMvbbeb8AM017OcfU4YSJzccuzq
+hOIPLL7b/PrK9YWR/W2fJbIh5NJ3GRx9ji7HWpKMZpwrnvEq/1s705GIQL7Pv3Ocxsw6BM
+ynzt4VdwZrpLYE9fdawx1GxLkifViat1Rmgf3PnxwOyBB1Vlx1RTVQiBHMBpDBhlMdCBPK
+hM8tFd5EpXZoFgoCEXqlssIptaf0zUZAgeES31GwNrP7+n6SlL+xZxbs7ykWKjA1ibYDTE
+fLIojOQCOgnIFaDFbbgUiqxoYg1SAr2SRPOjopc5EXSt5kfCdQk3I5MKoSm2INNuwBqprI
+/BL0Do3VowAQkxjXJUWit9RR0wS7FiA54WJqOrfU+2ChRooVUvtt0i/0y9tJMr6+PJYawZ
+uLwQ4DXs3UNVFKdopyh+zcLht+1xIZO6VrMteXejVhcz8UnRQE7leCdOvCYbBX87eRAAAA
+wFX+Q0Cp8SD3Pf607nq0GNYgY+vQR/zqqTnIlYt3kKt0jP6TuvkOUnaANOt29W17LxIR1U
+tZxFfyktrT4/RiVRP+QWvdLS32IN3mpCPt+J0oKujlSWrUT0SJgd7ZLePJIOscjuFqW72M
+dNV7aCNIisc2QgJbp5EwCNTFxQmdqryk9Pd80kWSSAwWQ5A6jXSrLEAdaFTa9kF/o3DGNe
+E/6HOTnt7BFvdE1hetYFnUTR1vqjD21Pi3d5rnePYUOGI1nAAAAMEA9fDwdHOCjosfA1ri
+pWZDYYkR5JaxrCFZC5h2LcYL01aCutISH07Z5gmnAVM+CfHkihck9wiGDJuJNTo1mYR1pg
+aFgoIFg8LjZzBConlSnHTPYkIYHvYFaE9T4PIS8yjlHjaDn59P06nHRNa5W/4vvhGK8eEn
+hpBCQ68huqMZyCH9az6BYR1TasHY7GMbIuMXpswXt9tWRzXpuvQq0BFThNelviTw3JNFhC
+cdR2+2wMCErnT0w1Y9fd+8SIHL3OdVAAAAwQDTLPokbpKxlOQOdKMGgn0CZnhiAiLMKp6k
+ZQmqNjVEdiOkLWvmoBYMxW93n6uCpyc7p9R+++xXWvfIH7o4fCpiIcMKQtd7Ikp4s4uC6q
+xP0QOtGui5wac/iBgd0QB7ZlRh0WNIRS0ej5sn3wHz7es5eq1F/auGhxR6i5N0EhB/qI72
+lFgWtRJsIxi0tm424K6XWEjjj7fe7k9qQ712BVOvvhZp1OK/qfsOl6lj7VPXtcVPr9+6Aw
+MsHc/xbLoyRmUAAAAUc3Rhbmh1QGpldC1hcm0ubG9jYWwBAgMEBQYH
+-----END OPENSSH PRIVATE KEY-----
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server2.pub b/internal/testhelper/testdata/testroot/certs/valid/server2.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server2.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDK4MvpmiZ0cLS4+p3YEwcCwo4kVbJPTEeiIMuoEI7KJ3yqAjKJuns6VpnC6aRgu3LmuM4uBHcimQi415ClBOm4+tFiVsVNcAksA+QuU8rTEC6xPs96y1Y/qC+/WQn6+uKp1+fAspWsLpig3VXSTfq+YAcxZfTdO/6ck0kHVvxX096Ye7D0mdq2lWbGwSBlAbzU1wX/Znv5hLZD4DSG1cIjA9vQ/fJ9pwclZiS0qQ1VXdoIUAvL9tTAKj295VGT2NMGGYZQAQ0vXtM1YHOMAZ4XoPL1oVFjVEZoLRD58a6Dpe4hY8QKe6X1w7zU1rr5vVYrz7MTUa5pzsUSOeUTc3kyKJrExVkoLyBgYQq8qW9kYk/Ox9zHwTAKw9vwiIs2Mwe6nlxJ7cw4zykMsxPK4z9HkbZoTFWy3phuPFqs/WQoCvHTKNjWPab7UAameM6Dn0N83BF21tCTMWjkRCvtZVGIGZ7Y4cAiiN0OPzQWDasJ/IKVDRguZJRn3kgHCMYCgok=
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1656518256 0
#      Wed Jun 29 15:57:36 2022 +0000
# Node ID 95477d444999b8feb935a8a64f458119bb6931a6
# Parent  0093de29bc5020319e5d6ef4a9be0f4b09b46a1e
# Parent  c1924280df1446887110459c5ad1aeb05f9efb1b
Merge branch 'sh-sshd-add-host-cert-support' into 'main'

gitlab-sshd: Add support for configuring host certificates

See merge request gitlab-org/gitlab-shell!661

diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -99,3 +99,7 @@
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
     - /run/secrets/ssh-hostkeys/ssh_host_ecdsa_key
     - /run/secrets/ssh-hostkeys/ssh_host_ed25519_key
+  host_key_certs:
+    - /run/secrets/ssh-hostkeys/ssh_host_rsa_key-cert.pub
+    - /run/secrets/ssh-hostkeys/ssh_host_ecdsa_key-cert.pub
+    - /run/secrets/ssh-hostkeys/ssh_host_ed25519_key-cert.pub
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -36,6 +36,7 @@
 	ReadinessProbe          string       `yaml:"readiness_probe"`
 	LivenessProbe           string       `yaml:"liveness_probe"`
 	HostKeyFiles            []string     `yaml:"host_key_files,omitempty"`
+	HostCertFiles           []string     `yaml:"host_cert_files,omitempty"`
 	MACs                    []string     `yaml:"macs"`
 	KexAlgorithms           []string     `yaml:"kex_algorithms"`
 	Ciphers                 []string     `yaml:"ciphers"`
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -39,17 +39,14 @@
 type serverConfig struct {
 	cfg                  *config.Config
 	hostKeys             []ssh.Signer
+	hostKeyToCertMap     map[string]*ssh.Certificate
 	authorizedKeysClient *authorizedkeys.Client
 }
 
-func newServerConfig(cfg *config.Config) (*serverConfig, error) {
-	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
-	if err != nil {
-		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
-	}
+func parseHostKeys(keyFiles []string) []ssh.Signer {
+	var hostKeys []ssh.Signer
 
-	var hostKeys []ssh.Signer
-	for _, filename := range cfg.Server.HostKeyFiles {
+	for _, filename := range keyFiles {
 		keyRaw, err := os.ReadFile(filename)
 		if err != nil {
 			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("Failed to read host key")
@@ -63,11 +60,70 @@
 
 		hostKeys = append(hostKeys, key)
 	}
+
+	return hostKeys
+}
+
+func parseHostCerts(hostKeys []ssh.Signer, certFiles []string) map[string]*ssh.Certificate {
+	keyToCertMap := map[string]*ssh.Certificate{}
+	hostKeyIndex := make(map[string]int)
+
+	for index, hostKey := range hostKeys {
+		hostKeyIndex[string(hostKey.PublicKey().Marshal())] = index
+	}
+
+	for _, filename := range certFiles {
+		keyRaw, err := os.ReadFile(filename)
+		if err != nil {
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("failed to read host certificate")
+			continue
+		}
+		publicKey, _, _, _, err := ssh.ParseAuthorizedKey(keyRaw)
+		if err != nil {
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("failed to parse host certificate")
+			continue
+		}
+
+		cert, ok := publicKey.(*ssh.Certificate)
+		if !ok {
+			log.WithFields(log.Fields{"filename": filename}).Warn("failed to decode host certificate")
+			continue
+		}
+
+		hostRawKey := string(cert.Key.Marshal())
+		index, found := hostKeyIndex[hostRawKey]
+		if found {
+			keyToCertMap[hostRawKey] = cert
+
+			certSigner, err := ssh.NewCertSigner(cert, hostKeys[index])
+			if err != nil {
+				log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("the host certificate doesn't match the host private key")
+				continue
+			}
+
+			hostKeys[index] = certSigner
+		} else {
+			log.WithFields(log.Fields{"filename": filename}).Warnf("no matching private key for certificate %s", filename)
+		}
+	}
+
+	return keyToCertMap
+}
+
+func newServerConfig(cfg *config.Config) (*serverConfig, error) {
+	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+	}
+
+	hostKeys := parseHostKeys(cfg.Server.HostKeyFiles)
 	if len(hostKeys) == 0 {
 		return nil, fmt.Errorf("No host keys could be loaded, aborting")
 	}
 
-	return &serverConfig{cfg: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
+	hostKeyToCertMap := parseHostCerts(hostKeys, cfg.Server.HostCertFiles)
+
+	return &serverConfig{cfg: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys, hostKeyToCertMap: hostKeyToCertMap}, nil
 }
 
 func (s *serverConfig) getAuthKey(ctx context.Context, user string, key ssh.PublicKey) (*authorizedkeys.Response, error) {
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
--- a/internal/sshd/server_config_test.go
+++ b/internal/sshd/server_config_test.go
@@ -5,6 +5,7 @@
 	"crypto/dsa"
 	"crypto/rand"
 	"crypto/rsa"
+	"os"
 	"path"
 	"testing"
 
@@ -22,6 +23,45 @@
 	require.Equal(t, "No host keys could be loaded, aborting", err.Error())
 }
 
+func TestHostKeyAndCerts(t *testing.T) {
+	testhelper.PrepareTestRootDir(t)
+
+	srvCfg := config.ServerConfig{
+		Listen:                  "127.0.0.1",
+		ConcurrentSessionsLimit: 1,
+		HostKeyFiles: []string{
+			path.Join(testhelper.TestRoot, "certs/valid/server.key"),
+		},
+		HostCertFiles: []string{
+			path.Join(testhelper.TestRoot, "certs/valid/server-cert.pub"),
+			path.Join(testhelper.TestRoot, "certs/valid/server2-cert.pub"),
+			path.Join(testhelper.TestRoot, "certs/invalid/server-cert.pub"),
+			path.Join(testhelper.TestRoot, "certs/invalid-path.key"),
+			path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+		},
+	}
+
+	cfg, err := newServerConfig(
+		&config.Config{GitlabUrl: "http://localhost", User: "user", Server: srvCfg},
+	)
+	require.NoError(t, err)
+
+	require.Len(t, cfg.hostKeys, 1)
+	require.Len(t, cfg.hostKeyToCertMap, 1)
+
+	// Check that the entry is pointing to the server's public key
+	data, err := os.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server.pub"))
+	require.NoError(t, err)
+
+	publicKey, _, _, _, err := ssh.ParseAuthorizedKey(data)
+	require.NoError(t, err)
+	require.NotNil(t, publicKey)
+	cert, ok := cfg.hostKeyToCertMap[string(publicKey.Marshal())]
+	require.True(t, ok)
+	require.NotNil(t, cert)
+	require.Equal(t, cert, cfg.hostKeys[0].PublicKey())
+}
+
 func TestFailedAuthorizedKeysClient(t *testing.T) {
 	_, err := newServerConfig(&config.Config{GitlabUrl: "ftp://localhost"})
 
diff --git a/internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub b/internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub
@@ -0,0 +1,1 @@
+ssh-rsa-cert-v01@openssh.com AAAAB3NzaC1yc2EAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yql not_a_valid_cert.pub
diff --git a/internal/testhelper/testdata/testroot/certs/valid/ca b/internal/testhelper/testdata/testroot/certs/valid/ca
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/ca
@@ -0,0 +1,38 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAYEA3IUBN80/BJG5ut9U238FwIGAfMZQoOXYbFtiQZo+U+cdbGJlEGgV
++IToGN78YJypkbK8NiptMMOgWZ8eu6DCJMNxjPb7e3XYGrmHPeoK5Z9ioESDw0QSkxDIfw
+FoLSnx0+eiXS138ypENgLT/hOMl+fmroA74ZpvkvIpK+RoAo1qlWCWtA7NygkQkWRgnnAj
+4jkDKq9aT1iHGa9nQYLuTtfiX3l35/oOOMJFrDg7bb5EHnBsKewDoAxdqNbxp4W/sQyUF4
+NbOFLGzRavA6eNRfOx9UXcdy0v9+UgCmu92nbiPmNBo9wY3D9prwXAWetTM1B3ZCDk1hqI
+cXRg6pNBNC9e2Fubchs9TcrLaZpr4kgFvTekGcof1m2ozJTVlcIJlJpy95mp1uXwxTPbfX
+KGPvKu7NroR2avkAIfwRj5E/8HwrgDH2r/4uRPadRCx9hm38HI1n6S4YrrsmL9ffLW6SCf
+EG6c7+URDQr18Vq9YyxMmu09EK7VlDDX0JM0Uan5AAAFkA5fIlIOXyJSAAAAB3NzaC1yc2
+EAAAGBANyFATfNPwSRubrfVNt/BcCBgHzGUKDl2GxbYkGaPlPnHWxiZRBoFfiE6Bje/GCc
+qZGyvDYqbTDDoFmfHrugwiTDcYz2+3t12Bq5hz3qCuWfYqBEg8NEEpMQyH8BaC0p8dPnol
+0td/MqRDYC0/4TjJfn5q6AO+Gab5LyKSvkaAKNapVglrQOzcoJEJFkYJ5wI+I5AyqvWk9Y
+hxmvZ0GC7k7X4l95d+f6DjjCRaw4O22+RB5wbCnsA6AMXajW8aeFv7EMlBeDWzhSxs0Wrw
+OnjUXzsfVF3HctL/flIAprvdp24j5jQaPcGNw/aa8FwFnrUzNQd2Qg5NYaiHF0YOqTQTQv
+Xthbm3IbPU3Ky2maa+JIBb03pBnKH9ZtqMyU1ZXCCZSacveZqdbl8MUz231yhj7yruza6E
+dmr5ACH8EY+RP/B8K4Ax9q/+LkT2nUQsfYZt/ByNZ+kuGK67Ji/X3y1ukgnxBunO/lEQ0K
+9fFavWMsTJrtPRCu1ZQw19CTNFGp+QAAAAMBAAEAAAGACHnYRSPPc0aCpAsngNRODUss/B
+7HRJfxDKEqkqjyElmEyQCzL8FAbu/019fiTXhYEDCViWNyFPi/9hHmpYGVVMJqX+eyXNl3
+t/c/moKfbpoEuXJIuj2olRyFCFSug2XkVKfHlttDjAYo3waWzWJE+iXAuR5WruI3vacvK+
++4i7iRyzIOONeE02orx9ra19wplO1qEL7ysrANaVBToLH+pOspWVAa6sCywT2+XdM/fYVd
+qunZTncy4Hj5NJ8mZLEATfJKnT2v7C47fBjN+ylqpyTImBZxSfVyjrljcQXb9ExjAhVTjv
+tBuZdB1NPnok9cycwpg6aGXuZX2mSQWROhHM/r80kUzfxJpRDs/AqMWRZYC2k/kCKbXg7S
+1cuAwJ2SiH5jslekhbB8bCU3rL2SgUV4oZsqh5fb6ZsytXarbzX/8Kcmb4KGsjZ7wBD6Yu
+sJ05TkzC/HkOT3xTXwyzZpEldKucLClnY3Boq8pkO1EoUD8uPJNgSgukH9W5SleaIxAAAA
+wEzXR8Av4SxsgWW2pPVtQaeKhUKrid21RuPF5/7c4PZ4GlnhL3Fod4PwdD8yPjuIuI7s6/
+9HRxzi+vr616/BXMagGWPSZEQMaX6I/L5CSratN2Dk1jSYcH501GseILr+kIcZhe7HoEf2
+xbr8ByF88DXpeSdimIqMeVYTPGWac7oSf3Y5WHi9FUuJ4BEccu8bLIXWkGMK6yi/zJo1RQ
+u4aMzdMyzat0C2aeAm40HABdUv350K/H20Voj7zfhmlXvQ7wAAAMEA8a1oEPFL1+cAqfUD
+Jbx+KWyw/1kFBIpU93rk2qfJR593nMLebAk0l9qfhbvlN6GTNcGETjzGK1bHaD9G14c2IT
+bFcIoKmah6LygIlMGwdTMSWPPrczeIhMy6H0rJ2lDa208+nLwKqlFlMDYNpycL2Q1ZynnB
+fYqfRiUSDJcs+2jfTX0gA17NuSwqp6j/JlMm45tN3GK1neIVH+4PBazBXqZTzdfCfqJ9r5
+TWJw2i6CsSlCDAtO3uo+Pyj327RbNtAAAAwQDplpqK2+0+QiQB+LUeT0hfWp5/HmXJjfgm
+u+xIICJKPqOYwDvBWEHHssv7YS70dFJrbENJ66NZfdv+foDbQXrr10odbIntk9QoO4CS1g
+zd63kolFCLhbwkYos45CjJIPuzNDeiYIgsLEOQwnjHbp3HxAIywxtUPKj80YmfoogeidiD
+JNMwRoJfqlNziW1PDq0r8Zhw2lbyGZPI218ox7tsJ94BS4MFJfgASwO9qcDsaYz23sS8uQ
+BBbY6cCknC7T0AAAAUc3Rhbmh1QGpldC1hcm0ubG9jYWwBAgMEBQYH
+-----END OPENSSH PRIVATE KEY-----
diff --git a/internal/testhelper/testdata/testroot/certs/valid/ca.pub b/internal/testhelper/testdata/testroot/certs/valid/ca.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/ca.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDchQE3zT8Ekbm631TbfwXAgYB8xlCg5dhsW2JBmj5T5x1sYmUQaBX4hOgY3vxgnKmRsrw2Km0ww6BZnx67oMIkw3GM9vt7ddgauYc96grln2KgRIPDRBKTEMh/AWgtKfHT56JdLXfzKkQ2AtP+E4yX5+augDvhmm+S8ikr5GgCjWqVYJa0Ds3KCRCRZGCecCPiOQMqr1pPWIcZr2dBgu5O1+JfeXfn+g44wkWsODttvkQecGwp7AOgDF2o1vGnhb+xDJQXg1s4UsbNFq8Dp41F87H1Rdx3LS/35SAKa73aduI+Y0Gj3BjcP2mvBcBZ61MzUHdkIOTWGohxdGDqk0E0L17YW5tyGz1NystpmmviSAW9N6QZyh/WbajMlNWVwgmUmnL3manW5fDFM9t9coY+8q7s2uhHZq+QAh/BGPkT/wfCuAMfav/i5E9p1ELH2GbfwcjWfpLhiuuyYv198tbpIJ8Qbpzv5RENCvXxWr1jLEya7T0QrtWUMNfQkzRRqfk= test@test.example.org
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server-cert.pub b/internal/testhelper/testdata/testroot/certs/valid/server-cert.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server-cert.pub
@@ -0,0 +1,1 @@
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgNGUtD+qw0Xj5NU2uj4+4LoCWPcvXP54F9Adw/hWN5LAAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yqlAAAAAAAAAAAAAAACAAAABnNlcnZlcgAAAAAAAAAAAAAAAP//////////AAAAAAAAAAAAAAAAAAABlwAAAAdzc2gtcnNhAAAAAwEAAQAAAYEA3IUBN80/BJG5ut9U238FwIGAfMZQoOXYbFtiQZo+U+cdbGJlEGgV+IToGN78YJypkbK8NiptMMOgWZ8eu6DCJMNxjPb7e3XYGrmHPeoK5Z9ioESDw0QSkxDIfwFoLSnx0+eiXS138ypENgLT/hOMl+fmroA74ZpvkvIpK+RoAo1qlWCWtA7NygkQkWRgnnAj4jkDKq9aT1iHGa9nQYLuTtfiX3l35/oOOMJFrDg7bb5EHnBsKewDoAxdqNbxp4W/sQyUF4NbOFLGzRavA6eNRfOx9UXcdy0v9+UgCmu92nbiPmNBo9wY3D9prwXAWetTM1B3ZCDk1hqIcXRg6pNBNC9e2Fubchs9TcrLaZpr4kgFvTekGcof1m2ozJTVlcIJlJpy95mp1uXwxTPbfXKGPvKu7NroR2avkAIfwRj5E/8HwrgDH2r/4uRPadRCx9hm38HI1n6S4YrrsmL9ffLW6SCfEG6c7+URDQr18Vq9YyxMmu09EK7VlDDX0JM0Uan5AAABlAAAAAxyc2Etc2hhMi01MTIAAAGAapK5VzLDoRe88tnLORrH8VxLegTWPKGdn0k5Ye8tS5XUgd0N98gU669y4ErDNf0kPxlz40bjsfisEEtJ/N7m14IskCepScfZRh8w6QgPxbTOhmrc89xooqBUE50y5FU9hIIbzUrnEP+Dfu4IiFPToAguCa+KoAKiOX7lBQqEugV6uOWVZ2erPopEv+OiMLD8hXsuKKjQ+TomHZ/IjuXsFdXH7Vcl0VsPaAcxyCtDU7nCTJSTUoUZtMtwwpulp0e/zNmFrEn2Binz4jRaUlk3FM2fdbotviDQYeOY3npmxaWUvvQ/eKn0/DzUTAKAGr2LDa2XWnPhj51BS1XkNaUlnupdYmZ2Sok0R4U3bfVwokteREvAltGbXQSDtZwLS5NEY6vIdDrxpn5QRf9vGjqnc7piXxye9gcLne4YDUi24IhGyHrnWKCC0HjF7tuUhCOVKrqRdmHxRGWX3PlS8Xn6HHEPWU+YZnfT1V2W7LAFcDMozQbs4GPGzZdR3f3vCOJ8 server.pub
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server.pub b/internal/testhelper/testdata/testroot/certs/valid/server.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yql
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub b/internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub
@@ -0,0 +1,1 @@
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg3XO30wn4+lFxc5fLsanH4Pvu0OuKGHoR94zZjxx7qAEAAAADAQABAAABgQDK4MvpmiZ0cLS4+p3YEwcCwo4kVbJPTEeiIMuoEI7KJ3yqAjKJuns6VpnC6aRgu3LmuM4uBHcimQi415ClBOm4+tFiVsVNcAksA+QuU8rTEC6xPs96y1Y/qC+/WQn6+uKp1+fAspWsLpig3VXSTfq+YAcxZfTdO/6ck0kHVvxX096Ye7D0mdq2lWbGwSBlAbzU1wX/Znv5hLZD4DSG1cIjA9vQ/fJ9pwclZiS0qQ1VXdoIUAvL9tTAKj295VGT2NMGGYZQAQ0vXtM1YHOMAZ4XoPL1oVFjVEZoLRD58a6Dpe4hY8QKe6X1w7zU1rr5vVYrz7MTUa5pzsUSOeUTc3kyKJrExVkoLyBgYQq8qW9kYk/Ox9zHwTAKw9vwiIs2Mwe6nlxJ7cw4zykMsxPK4z9HkbZoTFWy3phuPFqs/WQoCvHTKNjWPab7UAameM6Dn0N83BF21tCTMWjkRCvtZVGIGZ7Y4cAiiN0OPzQWDasJ/IKVDRguZJRn3kgHCMYCgokAAAAAAAAAAAAAAAIAAAAHc2VydmVyMgAAAAAAAAAAAAAAAP//////////AAAAAAAAAAAAAAAAAAABlwAAAAdzc2gtcnNhAAAAAwEAAQAAAYEA3IUBN80/BJG5ut9U238FwIGAfMZQoOXYbFtiQZo+U+cdbGJlEGgV+IToGN78YJypkbK8NiptMMOgWZ8eu6DCJMNxjPb7e3XYGrmHPeoK5Z9ioESDw0QSkxDIfwFoLSnx0+eiXS138ypENgLT/hOMl+fmroA74ZpvkvIpK+RoAo1qlWCWtA7NygkQkWRgnnAj4jkDKq9aT1iHGa9nQYLuTtfiX3l35/oOOMJFrDg7bb5EHnBsKewDoAxdqNbxp4W/sQyUF4NbOFLGzRavA6eNRfOx9UXcdy0v9+UgCmu92nbiPmNBo9wY3D9prwXAWetTM1B3ZCDk1hqIcXRg6pNBNC9e2Fubchs9TcrLaZpr4kgFvTekGcof1m2ozJTVlcIJlJpy95mp1uXwxTPbfXKGPvKu7NroR2avkAIfwRj5E/8HwrgDH2r/4uRPadRCx9hm38HI1n6S4YrrsmL9ffLW6SCfEG6c7+URDQr18Vq9YyxMmu09EK7VlDDX0JM0Uan5AAABlAAAAAxyc2Etc2hhMi01MTIAAAGANOKG8Tq7kp9B5+CQEyb+mEatJOoQRV+4rpemWlfEw6TuVwQN2wSXc6XKBHzSG4NRnFkwk6GgiPLQEf4lBBKA8VYQnDKuhrHJlU4DFCRPw/aceHfCwNOruyJmuf91W3yEO/kYAd6EhkQiW/K3ky7BuXCqR34T2fBZSCeYhNcXWxhEMLoAuj0kEdX+YMNBmiPtinPE13KMFGyIVBm/ojgSZa8j4WnhDcK0cWv0OSGTgJF6q3hENCWRz2E1HroKUiABOy5Nca6gPVAi4OTd7gwER8eh9MngVHYorAJ3N9HjUh640SbL3zCC8f/lqIztqsHY0u3olsQ0gLXpFain+430HeyJlmVlsDZgQKRb90Mm1viSCKvHGpmVDYMimE9y0DCQS1i0yRGF1uSIPtuQ0NCbhS/HPKsT3nYgGCEuoB8aGOu3aGB/tmUkYXW+pwXRKqw0f/zX088XWYWvA+AR4hmmr6DDMnf/4EHgJp3xHTEwOBHCVj69xvlOawBNlL2X0b2p server2.pub
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server2.key b/internal/testhelper/testdata/testroot/certs/valid/server2.key
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server2.key
@@ -0,0 +1,38 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAYEAyuDL6ZomdHC0uPqd2BMHAsKOJFWyT0xHoiDLqBCOyid8qgIyibp7
+OlaZwumkYLty5rjOLgR3IpkIuNeQpQTpuPrRYlbFTXAJLAPkLlPK0xAusT7PestWP6gvv1
+kJ+vriqdfnwLKVrC6YoN1V0k36vmAHMWX03Tv+nJNJB1b8V9PemHuw9JnatpVmxsEgZQG8
+1NcF/2Z7+YS2Q+A0htXCIwPb0P3yfacHJWYktKkNVV3aCFALy/bUwCo9veVRk9jTBhmGUA
+ENL17TNWBzjAGeF6Dy9aFRY1RGaC0Q+fGug6XuIWPECnul9cO81Na6+b1WK8+zE1Guac7F
+EjnlE3N5MiiaxMVZKC8gYGEKvKlvZGJPzsfcx8EwCsPb8IiLNjMHup5cSe3MOM8pDLMTyu
+M/R5G2aExVst6YbjxarP1kKArx0yjY1j2m+1AGpnjOg59DfNwRdtbQkzFo5EQr7WVRiBme
+2OHAIojdDj80Fg2rCfyClQ0YLmSUZ95IBwjGAoKJAAAFkBOZmjETmZoxAAAAB3NzaC1yc2
+EAAAGBAMrgy+maJnRwtLj6ndgTBwLCjiRVsk9MR6Igy6gQjsonfKoCMom6ezpWmcLppGC7
+cua4zi4EdyKZCLjXkKUE6bj60WJWxU1wCSwD5C5TytMQLrE+z3rLVj+oL79ZCfr64qnX58
+CylawumKDdVdJN+r5gBzFl9N07/pyTSQdW/FfT3ph7sPSZ2raVZsbBIGUBvNTXBf9me/mE
+tkPgNIbVwiMD29D98n2nByVmJLSpDVVd2ghQC8v21MAqPb3lUZPY0wYZhlABDS9e0zVgc4
+wBnheg8vWhUWNURmgtEPnxroOl7iFjxAp7pfXDvNTWuvm9VivPsxNRrmnOxRI55RNzeTIo
+msTFWSgvIGBhCrypb2RiT87H3MfBMArD2/CIizYzB7qeXEntzDjPKQyzE8rjP0eRtmhMVb
+LemG48Wqz9ZCgK8dMo2NY9pvtQBqZ4zoOfQ3zcEXbW0JMxaOREK+1lUYgZntjhwCKI3Q4/
+NBYNqwn8gpUNGC5klGfeSAcIxgKCiQAAAAMBAAEAAAGAUxojzMt85wNns8HMuD6LB6FkEh
+QcVwka6plecrhdlQb5tLXzt6DwayQgFcwYrhr6ZPHcWtMvbbeb8AM017OcfU4YSJzccuzq
+hOIPLL7b/PrK9YWR/W2fJbIh5NJ3GRx9ji7HWpKMZpwrnvEq/1s705GIQL7Pv3Ocxsw6BM
+ynzt4VdwZrpLYE9fdawx1GxLkifViat1Rmgf3PnxwOyBB1Vlx1RTVQiBHMBpDBhlMdCBPK
+hM8tFd5EpXZoFgoCEXqlssIptaf0zUZAgeES31GwNrP7+n6SlL+xZxbs7ykWKjA1ibYDTE
+fLIojOQCOgnIFaDFbbgUiqxoYg1SAr2SRPOjopc5EXSt5kfCdQk3I5MKoSm2INNuwBqprI
+/BL0Do3VowAQkxjXJUWit9RR0wS7FiA54WJqOrfU+2ChRooVUvtt0i/0y9tJMr6+PJYawZ
+uLwQ4DXs3UNVFKdopyh+zcLht+1xIZO6VrMteXejVhcz8UnRQE7leCdOvCYbBX87eRAAAA
+wFX+Q0Cp8SD3Pf607nq0GNYgY+vQR/zqqTnIlYt3kKt0jP6TuvkOUnaANOt29W17LxIR1U
+tZxFfyktrT4/RiVRP+QWvdLS32IN3mpCPt+J0oKujlSWrUT0SJgd7ZLePJIOscjuFqW72M
+dNV7aCNIisc2QgJbp5EwCNTFxQmdqryk9Pd80kWSSAwWQ5A6jXSrLEAdaFTa9kF/o3DGNe
+E/6HOTnt7BFvdE1hetYFnUTR1vqjD21Pi3d5rnePYUOGI1nAAAAMEA9fDwdHOCjosfA1ri
+pWZDYYkR5JaxrCFZC5h2LcYL01aCutISH07Z5gmnAVM+CfHkihck9wiGDJuJNTo1mYR1pg
+aFgoIFg8LjZzBConlSnHTPYkIYHvYFaE9T4PIS8yjlHjaDn59P06nHRNa5W/4vvhGK8eEn
+hpBCQ68huqMZyCH9az6BYR1TasHY7GMbIuMXpswXt9tWRzXpuvQq0BFThNelviTw3JNFhC
+cdR2+2wMCErnT0w1Y9fd+8SIHL3OdVAAAAwQDTLPokbpKxlOQOdKMGgn0CZnhiAiLMKp6k
+ZQmqNjVEdiOkLWvmoBYMxW93n6uCpyc7p9R+++xXWvfIH7o4fCpiIcMKQtd7Ikp4s4uC6q
+xP0QOtGui5wac/iBgd0QB7ZlRh0WNIRS0ej5sn3wHz7es5eq1F/auGhxR6i5N0EhB/qI72
+lFgWtRJsIxi0tm424K6XWEjjj7fe7k9qQ712BVOvvhZp1OK/qfsOl6lj7VPXtcVPr9+6Aw
+MsHc/xbLoyRmUAAAAUc3Rhbmh1QGpldC1hcm0ubG9jYWwBAgMEBQYH
+-----END OPENSSH PRIVATE KEY-----
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server2.pub b/internal/testhelper/testdata/testroot/certs/valid/server2.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server2.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDK4MvpmiZ0cLS4+p3YEwcCwo4kVbJPTEeiIMuoEI7KJ3yqAjKJuns6VpnC6aRgu3LmuM4uBHcimQi415ClBOm4+tFiVsVNcAksA+QuU8rTEC6xPs96y1Y/qC+/WQn6+uKp1+fAspWsLpig3VXSTfq+YAcxZfTdO/6ck0kHVvxX096Ye7D0mdq2lWbGwSBlAbzU1wX/Znv5hLZD4DSG1cIjA9vQ/fJ9pwclZiS0qQ1VXdoIUAvL9tTAKj295VGT2NMGGYZQAQ0vXtM1YHOMAZ4XoPL1oVFjVEZoLRD58a6Dpe4hY8QKe6X1w7zU1rr5vVYrz7MTUa5pzsUSOeUTc3kyKJrExVkoLyBgYQq8qW9kYk/Ox9zHwTAKw9vwiIs2Mwe6nlxJ7cw4zykMsxPK4z9HkbZoTFWy3phuPFqs/WQoCvHTKNjWPab7UAameM6Dn0N83BF21tCTMWjkRCvtZVGIGZ7Y4cAiiN0OPzQWDasJ/IKVDRguZJRn3kgHCMYCgok=
# HG changeset patch
# User Alejandro Rodríguez <alejorro70@gmail.com>
# Date 1656617851 -7200
#      Thu Jun 30 21:37:31 2022 +0200
# Node ID 6ea212dfc5cb500d23b7d98fe904c07bae53e992
# Parent  95477d444999b8feb935a8a64f458119bb6931a6
Pass original IP from PROXY requests to internal API calls

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -76,6 +76,7 @@
 			testErrorMessage(t, client)
 			testAuthenticationHeader(t, client)
 			testJWTAuthenticationHeader(t, client)
+			testXForwardedForHeader(t, client)
 		})
 	}
 }
@@ -221,6 +222,21 @@
 	})
 }
 
+func testXForwardedForHeader(t *testing.T, client *GitlabNetClient) {
+	t.Run("X-Forwarded-For Header inserted if original address in context", func(t *testing.T) {
+		ctx := context.WithValue(context.Background(), OriginalRemoteIPContextKey{}, "196.7.0.238")
+		response, err := client.Get(ctx, "/x_forwarded_for")
+		require.NoError(t, err)
+		require.NotNil(t, response)
+
+		defer response.Body.Close()
+
+		responseBody, err := io.ReadAll(response.Body)
+		require.NoError(t, err)
+		require.Equal(t, "196.7.0.238", string(responseBody))
+	})
+}
+
 func buildRequests(t *testing.T, relativeURLRoot string) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
@@ -257,6 +273,12 @@
 			},
 		},
 		{
+			Path: "/api/v4/internal/x_forwarded_for",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				fmt.Fprint(w, r.Header.Get("X-Forwarded-For"))
+			},
+		},
+		{
 			Path: "/api/v4/internal/error",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -41,6 +41,9 @@
 	Msg string
 }
 
+// To use as the key in a Context to set an X-Forwarded-For header in a request
+type OriginalRemoteIPContextKey struct{}
+
 func (e *ApiError) Error() string {
 	return e.Msg
 }
@@ -150,6 +153,11 @@
 	}
 	request.Header.Set(apiSecretHeaderName, tokenString)
 
+	originalRemoteIP, ok := ctx.Value(OriginalRemoteIPContextKey{}).(string)
+	if ok {
+		request.Header.Add("X-Forwarded-For", originalRemoteIP)
+	}
+
 	request.Header.Add("Content-Type", "application/json")
 	request.Header.Add("User-Agent", c.userAgent)
 	request.Close = true
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -3,7 +3,6 @@
 import (
 	"context"
 	"fmt"
-	"net"
 	"net/http"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
@@ -86,7 +85,7 @@
 		request.KeyId = args.GitlabKeyId
 	}
 
-	request.CheckIp = parseIP(args.Env.RemoteAddr)
+	request.CheckIp = gitlabnet.ParseIP(args.Env.RemoteAddr)
 
 	response, err := c.client.Post(ctx, "/allowed", request)
 	if err != nil {
@@ -117,18 +116,3 @@
 func (r *Response) IsCustomAction() bool {
 	return r.StatusCode == http.StatusMultipleChoices
 }
-
-func parseIP(remoteAddr string) string {
-	// The remoteAddr field can be filled by:
-	// 1. An IP address via the SSH_CONNECTION environment variable
-	// 2. A host:port combination via the PROXY protocol
-	ip, _, err := net.SplitHostPort(remoteAddr)
-
-	// If we don't have a port or can't parse this address for some reason,
-	// just return the original string.
-	if err != nil {
-		return remoteAddr
-	}
-
-	return ip
-}
diff --git a/internal/gitlabnet/client.go b/internal/gitlabnet/client.go
--- a/internal/gitlabnet/client.go
+++ b/internal/gitlabnet/client.go
@@ -3,6 +3,7 @@
 import (
 	"encoding/json"
 	"fmt"
+	"net"
 	"net/http"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
@@ -34,3 +35,18 @@
 
 	return nil
 }
+
+func ParseIP(remoteAddr string) string {
+	// The remoteAddr field can be filled by:
+	// 1. An IP address via the SSH_CONNECTION environment variable
+	// 2. A host:port combination via the PROXY protocol
+	ip, _, err := net.SplitHostPort(remoteAddr)
+
+	// If we don't have a port or can't parse this address for some reason,
+	// just return the original string.
+	if err != nil {
+		return remoteAddr
+	}
+
+	return ip
+}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -12,7 +12,9 @@
 	"github.com/pires/go-proxyproto"
 	"golang.org/x/crypto/ssh"
 
+	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
@@ -145,13 +147,26 @@
 	return s.status
 }
 
+func contextWithValues(parent context.Context, nconn net.Conn) context.Context {
+	ctx := correlation.ContextWithCorrelation(parent, correlation.SafeRandomID())
+
+	// If we're dealing with a PROXY connection, register the original requester's IP
+	mconn, ok := nconn.(*proxyproto.Conn)
+	if ok {
+		ip := gitlabnet.ParseIP(mconn.Raw().RemoteAddr().String())
+		ctx = context.WithValue(ctx, client.OriginalRemoteIPContextKey{}, ip)
+	}
+
+	return ctx
+}
+
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
 	defer s.wg.Done()
 
 	metrics.SshdConnectionsInFlight.Inc()
 	defer metrics.SshdConnectionsInFlight.Dec()
 
-	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
+	ctx, cancel := context.WithCancel(contextWithValues(ctx, nconn))
 	defer cancel()
 	go func() {
 		<-ctx.Done()
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -27,6 +27,7 @@
 
 var (
 	correlationId = ""
+	xForwardedFor = ""
 )
 
 func TestListenAndServe(t *testing.T) {
@@ -63,6 +64,10 @@
 		},
 		DestinationAddr: target,
 	}
+	xForwardedFor = "127.0.0.1"
+	defer func() {
+		xForwardedFor = "" // Cleanup for other test cases
+	}()
 
 	testCases := []struct {
 		desc        string
@@ -132,9 +137,9 @@
 				require.NoError(t, err)
 			}
 
-			sshConn, _, _, err := ssh.NewClientConn(conn, serverUrl, clientConfig(t))
+			sshConn, sshChans, sshRequs, err := ssh.NewClientConn(conn, serverUrl, clientConfig(t))
 			if sshConn != nil {
-				sshConn.Close()
+				defer sshConn.Close()
 			}
 
 			if tc.isRejected {
@@ -142,6 +147,10 @@
 				require.Regexp(t, "ssh: handshake failed", err.Error())
 			} else {
 				require.NoError(t, err)
+				client := ssh.NewClient(sshConn, sshChans, sshRequs)
+				defer client.Close()
+
+				holdSession(t, client)
 			}
 		})
 	}
@@ -306,6 +315,7 @@
 				correlationId = r.Header.Get("X-Request-Id")
 
 				require.NotEmpty(t, correlationId)
+				require.Equal(t, xForwardedFor, r.Header.Get("X-Forwarded-For"))
 
 				fmt.Fprint(w, `{"id": 1000, "key": "key"}`)
 			},
@@ -313,6 +323,7 @@
 			Path: "/api/v4/internal/discover",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				require.Equal(t, correlationId, r.Header.Get("X-Request-Id"))
+				require.Equal(t, xForwardedFor, r.Header.Get("X-Forwarded-For"))
 
 				fmt.Fprint(w, `{"id": 1000, "name": "Test User", "username": "test-user"}`)
 			},
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1656673379 0
#      Fri Jul 01 11:02:59 2022 +0000
# Node ID f780371e985f8a3e77a11e55731aae89cdee03a5
# Parent  95477d444999b8feb935a8a64f458119bb6931a6
# Parent  6ea212dfc5cb500d23b7d98fe904c07bae53e992
Merge branch 'sshd-forwarded-for' into 'main'

Pass original IP from PROXY requests to internal API calls

See merge request gitlab-org/gitlab-shell!665

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -76,6 +76,7 @@
 			testErrorMessage(t, client)
 			testAuthenticationHeader(t, client)
 			testJWTAuthenticationHeader(t, client)
+			testXForwardedForHeader(t, client)
 		})
 	}
 }
@@ -221,6 +222,21 @@
 	})
 }
 
+func testXForwardedForHeader(t *testing.T, client *GitlabNetClient) {
+	t.Run("X-Forwarded-For Header inserted if original address in context", func(t *testing.T) {
+		ctx := context.WithValue(context.Background(), OriginalRemoteIPContextKey{}, "196.7.0.238")
+		response, err := client.Get(ctx, "/x_forwarded_for")
+		require.NoError(t, err)
+		require.NotNil(t, response)
+
+		defer response.Body.Close()
+
+		responseBody, err := io.ReadAll(response.Body)
+		require.NoError(t, err)
+		require.Equal(t, "196.7.0.238", string(responseBody))
+	})
+}
+
 func buildRequests(t *testing.T, relativeURLRoot string) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
@@ -257,6 +273,12 @@
 			},
 		},
 		{
+			Path: "/api/v4/internal/x_forwarded_for",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				fmt.Fprint(w, r.Header.Get("X-Forwarded-For"))
+			},
+		},
+		{
 			Path: "/api/v4/internal/error",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -41,6 +41,9 @@
 	Msg string
 }
 
+// To use as the key in a Context to set an X-Forwarded-For header in a request
+type OriginalRemoteIPContextKey struct{}
+
 func (e *ApiError) Error() string {
 	return e.Msg
 }
@@ -150,6 +153,11 @@
 	}
 	request.Header.Set(apiSecretHeaderName, tokenString)
 
+	originalRemoteIP, ok := ctx.Value(OriginalRemoteIPContextKey{}).(string)
+	if ok {
+		request.Header.Add("X-Forwarded-For", originalRemoteIP)
+	}
+
 	request.Header.Add("Content-Type", "application/json")
 	request.Header.Add("User-Agent", c.userAgent)
 	request.Close = true
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -3,7 +3,6 @@
 import (
 	"context"
 	"fmt"
-	"net"
 	"net/http"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
@@ -86,7 +85,7 @@
 		request.KeyId = args.GitlabKeyId
 	}
 
-	request.CheckIp = parseIP(args.Env.RemoteAddr)
+	request.CheckIp = gitlabnet.ParseIP(args.Env.RemoteAddr)
 
 	response, err := c.client.Post(ctx, "/allowed", request)
 	if err != nil {
@@ -117,18 +116,3 @@
 func (r *Response) IsCustomAction() bool {
 	return r.StatusCode == http.StatusMultipleChoices
 }
-
-func parseIP(remoteAddr string) string {
-	// The remoteAddr field can be filled by:
-	// 1. An IP address via the SSH_CONNECTION environment variable
-	// 2. A host:port combination via the PROXY protocol
-	ip, _, err := net.SplitHostPort(remoteAddr)
-
-	// If we don't have a port or can't parse this address for some reason,
-	// just return the original string.
-	if err != nil {
-		return remoteAddr
-	}
-
-	return ip
-}
diff --git a/internal/gitlabnet/client.go b/internal/gitlabnet/client.go
--- a/internal/gitlabnet/client.go
+++ b/internal/gitlabnet/client.go
@@ -3,6 +3,7 @@
 import (
 	"encoding/json"
 	"fmt"
+	"net"
 	"net/http"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
@@ -34,3 +35,18 @@
 
 	return nil
 }
+
+func ParseIP(remoteAddr string) string {
+	// The remoteAddr field can be filled by:
+	// 1. An IP address via the SSH_CONNECTION environment variable
+	// 2. A host:port combination via the PROXY protocol
+	ip, _, err := net.SplitHostPort(remoteAddr)
+
+	// If we don't have a port or can't parse this address for some reason,
+	// just return the original string.
+	if err != nil {
+		return remoteAddr
+	}
+
+	return ip
+}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -12,7 +12,9 @@
 	"github.com/pires/go-proxyproto"
 	"golang.org/x/crypto/ssh"
 
+	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
@@ -145,13 +147,26 @@
 	return s.status
 }
 
+func contextWithValues(parent context.Context, nconn net.Conn) context.Context {
+	ctx := correlation.ContextWithCorrelation(parent, correlation.SafeRandomID())
+
+	// If we're dealing with a PROXY connection, register the original requester's IP
+	mconn, ok := nconn.(*proxyproto.Conn)
+	if ok {
+		ip := gitlabnet.ParseIP(mconn.Raw().RemoteAddr().String())
+		ctx = context.WithValue(ctx, client.OriginalRemoteIPContextKey{}, ip)
+	}
+
+	return ctx
+}
+
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
 	defer s.wg.Done()
 
 	metrics.SshdConnectionsInFlight.Inc()
 	defer metrics.SshdConnectionsInFlight.Dec()
 
-	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
+	ctx, cancel := context.WithCancel(contextWithValues(ctx, nconn))
 	defer cancel()
 	go func() {
 		<-ctx.Done()
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -27,6 +27,7 @@
 
 var (
 	correlationId = ""
+	xForwardedFor = ""
 )
 
 func TestListenAndServe(t *testing.T) {
@@ -63,6 +64,10 @@
 		},
 		DestinationAddr: target,
 	}
+	xForwardedFor = "127.0.0.1"
+	defer func() {
+		xForwardedFor = "" // Cleanup for other test cases
+	}()
 
 	testCases := []struct {
 		desc        string
@@ -132,9 +137,9 @@
 				require.NoError(t, err)
 			}
 
-			sshConn, _, _, err := ssh.NewClientConn(conn, serverUrl, clientConfig(t))
+			sshConn, sshChans, sshRequs, err := ssh.NewClientConn(conn, serverUrl, clientConfig(t))
 			if sshConn != nil {
-				sshConn.Close()
+				defer sshConn.Close()
 			}
 
 			if tc.isRejected {
@@ -142,6 +147,10 @@
 				require.Regexp(t, "ssh: handshake failed", err.Error())
 			} else {
 				require.NoError(t, err)
+				client := ssh.NewClient(sshConn, sshChans, sshRequs)
+				defer client.Close()
+
+				holdSession(t, client)
 			}
 		})
 	}
@@ -306,6 +315,7 @@
 				correlationId = r.Header.Get("X-Request-Id")
 
 				require.NotEmpty(t, correlationId)
+				require.Equal(t, xForwardedFor, r.Header.Get("X-Forwarded-For"))
 
 				fmt.Fprint(w, `{"id": 1000, "key": "key"}`)
 			},
@@ -313,6 +323,7 @@
 			Path: "/api/v4/internal/discover",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				require.Equal(t, correlationId, r.Header.Get("X-Request-Id"))
+				require.Equal(t, xForwardedFor, r.Header.Get("X-Forwarded-For"))
 
 				fmt.Fprint(w, `{"id": 1000, "name": "Test User", "username": "test-user"}`)
 			},
# HG changeset patch
# User Patrick Steinhardt <psteinhardt@gitlab.com>
# Date 1657003434 -7200
#      Tue Jul 05 08:43:54 2022 +0200
# Node ID 91f2a74d0d6cef9515a7c46e39f039aff8b12fe3
# Parent  f780371e985f8a3e77a11e55731aae89cdee03a5
go: Bump major version to v14

While gitlab-shell currently has a major version of v14, the module path
it exposes is not using that major version like it is required by the Go
standard. This makes it impossible for dependents to import gitlab-shell
as a dependency without using a commit as version.

Fix this by changing the module path of gitlab-shell to instead be
`gitlab.com/gitlab-org/gitlab-shell/v14` and adjust all imports
accordingly.

Changelog: fixed

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -15,8 +15,8 @@
 	"github.com/golang-jwt/jwt/v4"
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 var (
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -11,7 +11,7 @@
 	"time"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
 )
 
 func TestReadTimeout(t *testing.T) {
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -9,8 +9,8 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 //go:generate openssl req -newkey rsa:4096 -new -nodes -x509 -days 3650 -out ../internal/testhelper/testdata/testroot/certs/client/server.crt -keyout ../internal/testhelper/testdata/testroot/certs/client/key.pem -subj "/C=US/ST=California/L=San Francisco/O=GitLab/OU=GitLab-Shell/CN=localhost"
diff --git a/client/testserver/testserver.go b/client/testserver/testserver.go
--- a/client/testserver/testserver.go
+++ b/client/testserver/testserver.go
@@ -14,7 +14,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 var (
diff --git a/cmd/check/command/command.go b/cmd/check/command/command.go
--- a/cmd/check/command/command.go
+++ b/cmd/check/command/command.go
@@ -1,11 +1,11 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func New(config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/check/command/command_test.go b/cmd/check/command/command_test.go
--- a/cmd/check/command/command_test.go
+++ b/cmd/check/command/command_test.go
@@ -4,11 +4,11 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/cmd/check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/check/main.go b/cmd/check/main.go
--- a/cmd/check/main.go
+++ b/cmd/check/main.go
@@ -4,12 +4,12 @@
 	"fmt"
 	"os"
 
-	checkCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
+	checkCmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
 )
 
 func main() {
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command.go b/cmd/gitlab-shell-authorized-keys-check/command/command.go
--- a/cmd/gitlab-shell-authorized-keys-check/command/command.go
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command.go
@@ -1,12 +1,12 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command_test.go b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
--- a/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
@@ -4,12 +4,12 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-keys-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go
--- a/cmd/gitlab-shell-authorized-keys-check/main.go
+++ b/cmd/gitlab-shell-authorized-keys-check/main.go
@@ -4,13 +4,13 @@
 	"fmt"
 	"os"
 
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-keys-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
 )
 
 func main() {
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command.go b/cmd/gitlab-shell-authorized-principals-check/command/command.go
--- a/cmd/gitlab-shell-authorized-principals-check/command/command.go
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command.go
@@ -1,12 +1,12 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command_test.go b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
--- a/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
@@ -4,12 +4,12 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-principals-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go
--- a/cmd/gitlab-shell-authorized-principals-check/main.go
+++ b/cmd/gitlab-shell-authorized-principals-check/main.go
@@ -4,13 +4,13 @@
 	"fmt"
 	"os"
 
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-principals-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
 )
 
 func main() {
diff --git a/cmd/gitlab-shell/command/command.go b/cmd/gitlab-shell/command/command.go
--- a/cmd/gitlab-shell/command/command.go
+++ b/cmd/gitlab-shell/command/command.go
@@ -1,20 +1,20 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 func New(arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/gitlab-shell/command/command_test.go b/cmd/gitlab-shell/command/command_test.go
--- a/cmd/gitlab-shell/command/command_test.go
+++ b/cmd/gitlab-shell/command/command_test.go
@@ -5,20 +5,20 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -11,14 +11,14 @@
 	"gitlab.com/gitlab-org/labkit/fips"
 	"gitlab.com/gitlab-org/labkit/log"
 
-	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -24,7 +24,7 @@
 	"github.com/stretchr/testify/require"
 	gitalyClient "gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 	"golang.org/x/crypto/ssh"
 )
 
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -8,10 +8,10 @@
 	"syscall"
 	"time"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshd"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshd"
 
 	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/monitoring"
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module gitlab.com/gitlab-org/gitlab-shell
+module gitlab.com/gitlab-org/gitlab-shell/v14
 
 go 1.17
 
diff --git a/internal/command/authorizedkeys/authorized_keys.go b/internal/command/authorizedkeys/authorized_keys.go
--- a/internal/command/authorizedkeys/authorized_keys.go
+++ b/internal/command/authorizedkeys/authorized_keys.go
@@ -5,11 +5,11 @@
 	"fmt"
 	"strconv"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/keyline"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/keyline"
 )
 
 type Command struct {
diff --git a/internal/command/authorizedkeys/authorized_keys_test.go b/internal/command/authorizedkeys/authorized_keys_test.go
--- a/internal/command/authorizedkeys/authorized_keys_test.go
+++ b/internal/command/authorizedkeys/authorized_keys_test.go
@@ -9,10 +9,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/command/authorizedprincipals/authorized_principals.go b/internal/command/authorizedprincipals/authorized_principals.go
--- a/internal/command/authorizedprincipals/authorized_principals.go
+++ b/internal/command/authorizedprincipals/authorized_principals.go
@@ -4,10 +4,10 @@
 	"context"
 	"fmt"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/keyline"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/keyline"
 )
 
 type Command struct {
diff --git a/internal/command/authorizedprincipals/authorized_principals_test.go b/internal/command/authorizedprincipals/authorized_principals_test.go
--- a/internal/command/authorizedprincipals/authorized_principals_test.go
+++ b/internal/command/authorizedprincipals/authorized_principals_test.go
@@ -7,9 +7,9 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func TestExecute(t *testing.T) {
diff --git a/internal/command/command.go b/internal/command/command.go
--- a/internal/command/command.go
+++ b/internal/command/command.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/tracing"
 )
diff --git a/internal/command/command_test.go b/internal/command/command_test.go
--- a/internal/command/command_test.go
+++ b/internal/command/command_test.go
@@ -6,7 +6,7 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
diff --git a/internal/command/commandargs/shell.go b/internal/command/commandargs/shell.go
--- a/internal/command/commandargs/shell.go
+++ b/internal/command/commandargs/shell.go
@@ -6,7 +6,7 @@
 	"strings"
 
 	"github.com/mattn/go-shellwords"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 const (
diff --git a/internal/command/discover/discover.go b/internal/command/discover/discover.go
--- a/internal/command/discover/discover.go
+++ b/internal/command/discover/discover.go
@@ -4,10 +4,10 @@
 	"context"
 	"fmt"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Command struct {
diff --git a/internal/command/discover/discover_test.go b/internal/command/discover/discover_test.go
--- a/internal/command/discover/discover_test.go
+++ b/internal/command/discover/discover_test.go
@@ -10,10 +10,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/command/healthcheck/healthcheck.go b/internal/command/healthcheck/healthcheck.go
--- a/internal/command/healthcheck/healthcheck.go
+++ b/internal/command/healthcheck/healthcheck.go
@@ -4,9 +4,9 @@
 	"context"
 	"fmt"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/healthcheck"
 )
 
 var (
diff --git a/internal/command/healthcheck/healthcheck_test.go b/internal/command/healthcheck/healthcheck_test.go
--- a/internal/command/healthcheck/healthcheck_test.go
+++ b/internal/command/healthcheck/healthcheck_test.go
@@ -9,10 +9,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/healthcheck"
 )
 
 var (
diff --git a/internal/command/lfsauthenticate/lfsauthenticate.go b/internal/command/lfsauthenticate/lfsauthenticate.go
--- a/internal/command/lfsauthenticate/lfsauthenticate.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate.go
@@ -8,12 +8,12 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/lfsauthenticate"
 )
 
 const (
diff --git a/internal/command/lfsauthenticate/lfsauthenticate_test.go b/internal/command/lfsauthenticate/lfsauthenticate_test.go
--- a/internal/command/lfsauthenticate/lfsauthenticate_test.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate_test.go
@@ -10,13 +10,13 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestFailedRequests(t *testing.T) {
diff --git a/internal/command/personalaccesstoken/personalaccesstoken.go b/internal/command/personalaccesstoken/personalaccesstoken.go
--- a/internal/command/personalaccesstoken/personalaccesstoken.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken.go
@@ -10,10 +10,10 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/personalaccesstoken"
 )
 
 const (
diff --git a/internal/command/personalaccesstoken/personalaccesstoken_test.go b/internal/command/personalaccesstoken/personalaccesstoken_test.go
--- a/internal/command/personalaccesstoken/personalaccesstoken_test.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken_test.go
@@ -10,11 +10,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/personalaccesstoken"
 )
 
 var (
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -7,9 +7,9 @@
 
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/handler"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
diff --git a/internal/command/receivepack/gitalycall_test.go b/internal/command/receivepack/gitalycall_test.go
--- a/internal/command/receivepack/gitalycall_test.go
+++ b/internal/command/receivepack/gitalycall_test.go
@@ -8,12 +8,12 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestReceivePack(t *testing.T) {
diff --git a/internal/command/receivepack/receivepack.go b/internal/command/receivepack/receivepack.go
--- a/internal/command/receivepack/receivepack.go
+++ b/internal/command/receivepack/receivepack.go
@@ -3,12 +3,12 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/customaction"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/customaction"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 type Command struct {
diff --git a/internal/command/receivepack/receivepack_test.go b/internal/command/receivepack/receivepack_test.go
--- a/internal/command/receivepack/receivepack_test.go
+++ b/internal/command/receivepack/receivepack_test.go
@@ -7,11 +7,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestForbiddenAccess(t *testing.T) {
diff --git a/internal/command/shared/accessverifier/accessverifier.go b/internal/command/shared/accessverifier/accessverifier.go
--- a/internal/command/shared/accessverifier/accessverifier.go
+++ b/internal/command/shared/accessverifier/accessverifier.go
@@ -4,11 +4,11 @@
 	"context"
 	"errors"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 )
 
 type Response = accessverifier.Response
diff --git a/internal/command/shared/accessverifier/accessverifier_test.go b/internal/command/shared/accessverifier/accessverifier_test.go
--- a/internal/command/shared/accessverifier/accessverifier_test.go
+++ b/internal/command/shared/accessverifier/accessverifier_test.go
@@ -10,11 +10,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 )
 
 var (
diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -9,12 +9,12 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/pktline"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/pktline"
 )
 
 type Request struct {
diff --git a/internal/command/shared/customaction/customaction_test.go b/internal/command/shared/customaction/customaction_test.go
--- a/internal/command/shared/customaction/customaction_test.go
+++ b/internal/command/shared/customaction/customaction_test.go
@@ -10,10 +10,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 )
 
 func TestExecuteEOFSent(t *testing.T) {
diff --git a/internal/command/twofactorrecover/twofactorrecover.go b/internal/command/twofactorrecover/twofactorrecover.go
--- a/internal/command/twofactorrecover/twofactorrecover.go
+++ b/internal/command/twofactorrecover/twofactorrecover.go
@@ -8,10 +8,10 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorrecover"
 )
 
 const readerLimit = 1024
diff --git a/internal/command/twofactorrecover/twofactorrecover_test.go b/internal/command/twofactorrecover/twofactorrecover_test.go
--- a/internal/command/twofactorrecover/twofactorrecover_test.go
+++ b/internal/command/twofactorrecover/twofactorrecover_test.go
@@ -11,11 +11,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorrecover"
 )
 
 var (
diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -7,10 +7,10 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
 )
 
 type Command struct {
diff --git a/internal/command/twofactorverify/twofactorverify_test.go b/internal/command/twofactorverify/twofactorverify_test.go
--- a/internal/command/twofactorverify/twofactorverify_test.go
+++ b/internal/command/twofactorverify/twofactorverify_test.go
@@ -10,11 +10,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
 )
 
 func setup(t *testing.T) []testserver.TestRequestHandler {
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -7,9 +7,9 @@
 
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/handler"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
diff --git a/internal/command/uploadarchive/gitalycall_test.go b/internal/command/uploadarchive/gitalycall_test.go
--- a/internal/command/uploadarchive/gitalycall_test.go
+++ b/internal/command/uploadarchive/gitalycall_test.go
@@ -8,12 +8,12 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestUploadArchive(t *testing.T) {
diff --git a/internal/command/uploadarchive/uploadarchive.go b/internal/command/uploadarchive/uploadarchive.go
--- a/internal/command/uploadarchive/uploadarchive.go
+++ b/internal/command/uploadarchive/uploadarchive.go
@@ -3,11 +3,11 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 type Command struct {
diff --git a/internal/command/uploadarchive/uploadarchive_test.go b/internal/command/uploadarchive/uploadarchive_test.go
--- a/internal/command/uploadarchive/uploadarchive_test.go
+++ b/internal/command/uploadarchive/uploadarchive_test.go
@@ -7,11 +7,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestForbiddenAccess(t *testing.T) {
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -7,9 +7,9 @@
 
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/handler"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -8,12 +8,12 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestUploadPack(t *testing.T) {
diff --git a/internal/command/uploadpack/uploadpack.go b/internal/command/uploadpack/uploadpack.go
--- a/internal/command/uploadpack/uploadpack.go
+++ b/internal/command/uploadpack/uploadpack.go
@@ -3,12 +3,12 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/customaction"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/customaction"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 type Command struct {
diff --git a/internal/command/uploadpack/uploadpack_test.go b/internal/command/uploadpack/uploadpack_test.go
--- a/internal/command/uploadpack/uploadpack_test.go
+++ b/internal/command/uploadpack/uploadpack_test.go
@@ -7,11 +7,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestForbiddenAccess(t *testing.T) {
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -11,9 +11,9 @@
 
 	yaml "gopkg.in/yaml.v2"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitaly"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 const (
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -9,8 +9,8 @@
 	"github.com/stretchr/testify/require"
 	yaml "gopkg.in/yaml.v2"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 func TestConfigApplyGlobalState(t *testing.T) {
diff --git a/internal/executable/executable_test.go b/internal/executable/executable_test.go
--- a/internal/executable/executable_test.go
+++ b/internal/executable/executable_test.go
@@ -4,7 +4,7 @@
 	"errors"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 
 	"github.com/stretchr/testify/require"
 )
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
--- a/internal/gitaly/gitaly.go
+++ b/internal/gitaly/gitaly.go
@@ -17,7 +17,7 @@
 	"gitlab.com/gitlab-org/labkit/log"
 	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 type Command struct {
diff --git a/internal/gitaly/gitaly_test.go b/internal/gitaly/gitaly_test.go
--- a/internal/gitaly/gitaly_test.go
+++ b/internal/gitaly/gitaly_test.go
@@ -7,7 +7,7 @@
 	"github.com/prometheus/client_golang/prometheus/testutil"
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 func TestPrometheusMetrics(t *testing.T) {
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -6,10 +6,10 @@
 	"net/http"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 const (
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -11,11 +11,11 @@
 
 	"github.com/stretchr/testify/require"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 var (
diff --git a/internal/gitlabnet/authorizedkeys/client.go b/internal/gitlabnet/authorizedkeys/client.go
--- a/internal/gitlabnet/authorizedkeys/client.go
+++ b/internal/gitlabnet/authorizedkeys/client.go
@@ -5,9 +5,9 @@
 	"fmt"
 	"net/url"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 const (
diff --git a/internal/gitlabnet/authorizedkeys/client_test.go b/internal/gitlabnet/authorizedkeys/client_test.go
--- a/internal/gitlabnet/authorizedkeys/client_test.go
+++ b/internal/gitlabnet/authorizedkeys/client_test.go
@@ -7,9 +7,9 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/gitlabnet/client.go b/internal/gitlabnet/client.go
--- a/internal/gitlabnet/client.go
+++ b/internal/gitlabnet/client.go
@@ -6,9 +6,9 @@
 	"net"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/gitlabnet/discover/client.go b/internal/gitlabnet/discover/client.go
--- a/internal/gitlabnet/discover/client.go
+++ b/internal/gitlabnet/discover/client.go
@@ -6,10 +6,10 @@
 	"net/http"
 	"net/url"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/discover/client_test.go b/internal/gitlabnet/discover/client_test.go
--- a/internal/gitlabnet/discover/client_test.go
+++ b/internal/gitlabnet/discover/client_test.go
@@ -8,11 +8,11 @@
 	"net/url"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/gitlabnet/healthcheck/client.go b/internal/gitlabnet/healthcheck/client.go
--- a/internal/gitlabnet/healthcheck/client.go
+++ b/internal/gitlabnet/healthcheck/client.go
@@ -5,9 +5,9 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 const (
diff --git a/internal/gitlabnet/healthcheck/client_test.go b/internal/gitlabnet/healthcheck/client_test.go
--- a/internal/gitlabnet/healthcheck/client_test.go
+++ b/internal/gitlabnet/healthcheck/client_test.go
@@ -6,8 +6,8 @@
 	"net/http"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 
 	"github.com/stretchr/testify/require"
 )
diff --git a/internal/gitlabnet/lfsauthenticate/client.go b/internal/gitlabnet/lfsauthenticate/client.go
--- a/internal/gitlabnet/lfsauthenticate/client.go
+++ b/internal/gitlabnet/lfsauthenticate/client.go
@@ -6,10 +6,10 @@
 	"net/http"
 	"strings"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/lfsauthenticate/client_test.go b/internal/gitlabnet/lfsauthenticate/client_test.go
--- a/internal/gitlabnet/lfsauthenticate/client_test.go
+++ b/internal/gitlabnet/lfsauthenticate/client_test.go
@@ -9,9 +9,9 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 const (
diff --git a/internal/gitlabnet/personalaccesstoken/client.go b/internal/gitlabnet/personalaccesstoken/client.go
--- a/internal/gitlabnet/personalaccesstoken/client.go
+++ b/internal/gitlabnet/personalaccesstoken/client.go
@@ -6,11 +6,11 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/personalaccesstoken/client_test.go b/internal/gitlabnet/personalaccesstoken/client_test.go
--- a/internal/gitlabnet/personalaccesstoken/client_test.go
+++ b/internal/gitlabnet/personalaccesstoken/client_test.go
@@ -8,11 +8,11 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 var (
diff --git a/internal/gitlabnet/twofactorrecover/client.go b/internal/gitlabnet/twofactorrecover/client.go
--- a/internal/gitlabnet/twofactorrecover/client.go
+++ b/internal/gitlabnet/twofactorrecover/client.go
@@ -6,11 +6,11 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/twofactorrecover/client_test.go b/internal/gitlabnet/twofactorrecover/client_test.go
--- a/internal/gitlabnet/twofactorrecover/client_test.go
+++ b/internal/gitlabnet/twofactorrecover/client_test.go
@@ -8,11 +8,11 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 var (
diff --git a/internal/gitlabnet/twofactorverify/client.go b/internal/gitlabnet/twofactorverify/client.go
--- a/internal/gitlabnet/twofactorverify/client.go
+++ b/internal/gitlabnet/twofactorverify/client.go
@@ -6,11 +6,11 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/twofactorverify/client_test.go b/internal/gitlabnet/twofactorverify/client_test.go
--- a/internal/gitlabnet/twofactorverify/client_test.go
+++ b/internal/gitlabnet/twofactorverify/client_test.go
@@ -7,13 +7,13 @@
 	"net/http"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func initialize(t *testing.T) []testserver.TestRequestHandler {
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -11,10 +11,10 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitaly"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/labkit/log"
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -12,10 +12,10 @@
 	grpcstatus "google.golang.org/grpc/status"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn) (int32, error) {
diff --git a/internal/keyline/key_line.go b/internal/keyline/key_line.go
--- a/internal/keyline/key_line.go
+++ b/internal/keyline/key_line.go
@@ -7,8 +7,8 @@
 	"regexp"
 	"strings"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
 )
 
 var (
diff --git a/internal/keyline/key_line_test.go b/internal/keyline/key_line_test.go
--- a/internal/keyline/key_line_test.go
+++ b/internal/keyline/key_line_test.go
@@ -4,7 +4,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func TestFailingNewPublicKeyLine(t *testing.T) {
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -9,7 +9,7 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func logFmt(inFmt string) string {
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -8,7 +8,7 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func TestConfigure(t *testing.T) {
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -13,10 +13,10 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/log"
 )
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -14,10 +14,10 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 type rejectCall struct {
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -10,8 +10,8 @@
 
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/authorizedkeys"
 
 	"gitlab.com/gitlab-org/labkit/log"
 )
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
--- a/internal/sshd/server_config_test.go
+++ b/internal/sshd/server_config_test.go
@@ -12,8 +12,8 @@
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 func TestNewServerConfigWithoutHosts(t *testing.T) {
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -12,13 +12,13 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
-	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 type session struct {
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -11,9 +11,9 @@
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
 )
 
 type fakeChannel struct {
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -12,10 +12,10 @@
 	"github.com/pires/go-proxyproto"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/log"
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -15,9 +15,9 @@
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 const (
diff --git a/internal/sshenv/sshenv_test.go b/internal/sshenv/sshenv_test.go
--- a/internal/sshenv/sshenv_test.go
+++ b/internal/sshenv/sshenv_test.go
@@ -4,7 +4,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 func TestNewFromEnv(t *testing.T) {
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -7,7 +7,7 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
 )
 
 func BuildDisallowedByApiHandlers(t *testing.T) []testserver.TestRequestHandler {
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1657012810 0
#      Tue Jul 05 09:20:10 2022 +0000
# Node ID 7fc6cf9719ad6dee23d206e5d3152971d1c1525c
# Parent  f780371e985f8a3e77a11e55731aae89cdee03a5
# Parent  91f2a74d0d6cef9515a7c46e39f039aff8b12fe3
Merge branch 'pks-go-module-path-version' into 'main'

go: Bump major version to v14

Closes #593

See merge request gitlab-org/gitlab-shell!666

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -15,8 +15,8 @@
 	"github.com/golang-jwt/jwt/v4"
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 var (
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -11,7 +11,7 @@
 	"time"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
 )
 
 func TestReadTimeout(t *testing.T) {
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -9,8 +9,8 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 //go:generate openssl req -newkey rsa:4096 -new -nodes -x509 -days 3650 -out ../internal/testhelper/testdata/testroot/certs/client/server.crt -keyout ../internal/testhelper/testdata/testroot/certs/client/key.pem -subj "/C=US/ST=California/L=San Francisco/O=GitLab/OU=GitLab-Shell/CN=localhost"
diff --git a/client/testserver/testserver.go b/client/testserver/testserver.go
--- a/client/testserver/testserver.go
+++ b/client/testserver/testserver.go
@@ -14,7 +14,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 var (
diff --git a/cmd/check/command/command.go b/cmd/check/command/command.go
--- a/cmd/check/command/command.go
+++ b/cmd/check/command/command.go
@@ -1,11 +1,11 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func New(config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/check/command/command_test.go b/cmd/check/command/command_test.go
--- a/cmd/check/command/command_test.go
+++ b/cmd/check/command/command_test.go
@@ -4,11 +4,11 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/cmd/check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/check/main.go b/cmd/check/main.go
--- a/cmd/check/main.go
+++ b/cmd/check/main.go
@@ -4,12 +4,12 @@
 	"fmt"
 	"os"
 
-	checkCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
+	checkCmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
 )
 
 func main() {
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command.go b/cmd/gitlab-shell-authorized-keys-check/command/command.go
--- a/cmd/gitlab-shell-authorized-keys-check/command/command.go
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command.go
@@ -1,12 +1,12 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command_test.go b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
--- a/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
@@ -4,12 +4,12 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-keys-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go
--- a/cmd/gitlab-shell-authorized-keys-check/main.go
+++ b/cmd/gitlab-shell-authorized-keys-check/main.go
@@ -4,13 +4,13 @@
 	"fmt"
 	"os"
 
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-keys-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
 )
 
 func main() {
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command.go b/cmd/gitlab-shell-authorized-principals-check/command/command.go
--- a/cmd/gitlab-shell-authorized-principals-check/command/command.go
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command.go
@@ -1,12 +1,12 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command_test.go b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
--- a/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
@@ -4,12 +4,12 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-principals-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go
--- a/cmd/gitlab-shell-authorized-principals-check/main.go
+++ b/cmd/gitlab-shell-authorized-principals-check/main.go
@@ -4,13 +4,13 @@
 	"fmt"
 	"os"
 
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-principals-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
 )
 
 func main() {
diff --git a/cmd/gitlab-shell/command/command.go b/cmd/gitlab-shell/command/command.go
--- a/cmd/gitlab-shell/command/command.go
+++ b/cmd/gitlab-shell/command/command.go
@@ -1,20 +1,20 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 func New(arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/gitlab-shell/command/command_test.go b/cmd/gitlab-shell/command/command_test.go
--- a/cmd/gitlab-shell/command/command_test.go
+++ b/cmd/gitlab-shell/command/command_test.go
@@ -5,20 +5,20 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -11,14 +11,14 @@
 	"gitlab.com/gitlab-org/labkit/fips"
 	"gitlab.com/gitlab-org/labkit/log"
 
-	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -24,7 +24,7 @@
 	"github.com/stretchr/testify/require"
 	gitalyClient "gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 	"golang.org/x/crypto/ssh"
 )
 
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -8,10 +8,10 @@
 	"syscall"
 	"time"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshd"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshd"
 
 	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/monitoring"
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module gitlab.com/gitlab-org/gitlab-shell
+module gitlab.com/gitlab-org/gitlab-shell/v14
 
 go 1.17
 
diff --git a/internal/command/authorizedkeys/authorized_keys.go b/internal/command/authorizedkeys/authorized_keys.go
--- a/internal/command/authorizedkeys/authorized_keys.go
+++ b/internal/command/authorizedkeys/authorized_keys.go
@@ -5,11 +5,11 @@
 	"fmt"
 	"strconv"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/keyline"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/keyline"
 )
 
 type Command struct {
diff --git a/internal/command/authorizedkeys/authorized_keys_test.go b/internal/command/authorizedkeys/authorized_keys_test.go
--- a/internal/command/authorizedkeys/authorized_keys_test.go
+++ b/internal/command/authorizedkeys/authorized_keys_test.go
@@ -9,10 +9,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/command/authorizedprincipals/authorized_principals.go b/internal/command/authorizedprincipals/authorized_principals.go
--- a/internal/command/authorizedprincipals/authorized_principals.go
+++ b/internal/command/authorizedprincipals/authorized_principals.go
@@ -4,10 +4,10 @@
 	"context"
 	"fmt"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/keyline"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/keyline"
 )
 
 type Command struct {
diff --git a/internal/command/authorizedprincipals/authorized_principals_test.go b/internal/command/authorizedprincipals/authorized_principals_test.go
--- a/internal/command/authorizedprincipals/authorized_principals_test.go
+++ b/internal/command/authorizedprincipals/authorized_principals_test.go
@@ -7,9 +7,9 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func TestExecute(t *testing.T) {
diff --git a/internal/command/command.go b/internal/command/command.go
--- a/internal/command/command.go
+++ b/internal/command/command.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/tracing"
 )
diff --git a/internal/command/command_test.go b/internal/command/command_test.go
--- a/internal/command/command_test.go
+++ b/internal/command/command_test.go
@@ -6,7 +6,7 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
diff --git a/internal/command/commandargs/shell.go b/internal/command/commandargs/shell.go
--- a/internal/command/commandargs/shell.go
+++ b/internal/command/commandargs/shell.go
@@ -6,7 +6,7 @@
 	"strings"
 
 	"github.com/mattn/go-shellwords"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 const (
diff --git a/internal/command/discover/discover.go b/internal/command/discover/discover.go
--- a/internal/command/discover/discover.go
+++ b/internal/command/discover/discover.go
@@ -4,10 +4,10 @@
 	"context"
 	"fmt"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Command struct {
diff --git a/internal/command/discover/discover_test.go b/internal/command/discover/discover_test.go
--- a/internal/command/discover/discover_test.go
+++ b/internal/command/discover/discover_test.go
@@ -10,10 +10,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/command/healthcheck/healthcheck.go b/internal/command/healthcheck/healthcheck.go
--- a/internal/command/healthcheck/healthcheck.go
+++ b/internal/command/healthcheck/healthcheck.go
@@ -4,9 +4,9 @@
 	"context"
 	"fmt"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/healthcheck"
 )
 
 var (
diff --git a/internal/command/healthcheck/healthcheck_test.go b/internal/command/healthcheck/healthcheck_test.go
--- a/internal/command/healthcheck/healthcheck_test.go
+++ b/internal/command/healthcheck/healthcheck_test.go
@@ -9,10 +9,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/healthcheck"
 )
 
 var (
diff --git a/internal/command/lfsauthenticate/lfsauthenticate.go b/internal/command/lfsauthenticate/lfsauthenticate.go
--- a/internal/command/lfsauthenticate/lfsauthenticate.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate.go
@@ -8,12 +8,12 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/lfsauthenticate"
 )
 
 const (
diff --git a/internal/command/lfsauthenticate/lfsauthenticate_test.go b/internal/command/lfsauthenticate/lfsauthenticate_test.go
--- a/internal/command/lfsauthenticate/lfsauthenticate_test.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate_test.go
@@ -10,13 +10,13 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestFailedRequests(t *testing.T) {
diff --git a/internal/command/personalaccesstoken/personalaccesstoken.go b/internal/command/personalaccesstoken/personalaccesstoken.go
--- a/internal/command/personalaccesstoken/personalaccesstoken.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken.go
@@ -10,10 +10,10 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/personalaccesstoken"
 )
 
 const (
diff --git a/internal/command/personalaccesstoken/personalaccesstoken_test.go b/internal/command/personalaccesstoken/personalaccesstoken_test.go
--- a/internal/command/personalaccesstoken/personalaccesstoken_test.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken_test.go
@@ -10,11 +10,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/personalaccesstoken"
 )
 
 var (
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -7,9 +7,9 @@
 
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/handler"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
diff --git a/internal/command/receivepack/gitalycall_test.go b/internal/command/receivepack/gitalycall_test.go
--- a/internal/command/receivepack/gitalycall_test.go
+++ b/internal/command/receivepack/gitalycall_test.go
@@ -8,12 +8,12 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestReceivePack(t *testing.T) {
diff --git a/internal/command/receivepack/receivepack.go b/internal/command/receivepack/receivepack.go
--- a/internal/command/receivepack/receivepack.go
+++ b/internal/command/receivepack/receivepack.go
@@ -3,12 +3,12 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/customaction"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/customaction"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 type Command struct {
diff --git a/internal/command/receivepack/receivepack_test.go b/internal/command/receivepack/receivepack_test.go
--- a/internal/command/receivepack/receivepack_test.go
+++ b/internal/command/receivepack/receivepack_test.go
@@ -7,11 +7,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestForbiddenAccess(t *testing.T) {
diff --git a/internal/command/shared/accessverifier/accessverifier.go b/internal/command/shared/accessverifier/accessverifier.go
--- a/internal/command/shared/accessverifier/accessverifier.go
+++ b/internal/command/shared/accessverifier/accessverifier.go
@@ -4,11 +4,11 @@
 	"context"
 	"errors"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 )
 
 type Response = accessverifier.Response
diff --git a/internal/command/shared/accessverifier/accessverifier_test.go b/internal/command/shared/accessverifier/accessverifier_test.go
--- a/internal/command/shared/accessverifier/accessverifier_test.go
+++ b/internal/command/shared/accessverifier/accessverifier_test.go
@@ -10,11 +10,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 )
 
 var (
diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -9,12 +9,12 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/pktline"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/pktline"
 )
 
 type Request struct {
diff --git a/internal/command/shared/customaction/customaction_test.go b/internal/command/shared/customaction/customaction_test.go
--- a/internal/command/shared/customaction/customaction_test.go
+++ b/internal/command/shared/customaction/customaction_test.go
@@ -10,10 +10,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 )
 
 func TestExecuteEOFSent(t *testing.T) {
diff --git a/internal/command/twofactorrecover/twofactorrecover.go b/internal/command/twofactorrecover/twofactorrecover.go
--- a/internal/command/twofactorrecover/twofactorrecover.go
+++ b/internal/command/twofactorrecover/twofactorrecover.go
@@ -8,10 +8,10 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorrecover"
 )
 
 const readerLimit = 1024
diff --git a/internal/command/twofactorrecover/twofactorrecover_test.go b/internal/command/twofactorrecover/twofactorrecover_test.go
--- a/internal/command/twofactorrecover/twofactorrecover_test.go
+++ b/internal/command/twofactorrecover/twofactorrecover_test.go
@@ -11,11 +11,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorrecover"
 )
 
 var (
diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -7,10 +7,10 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
 )
 
 type Command struct {
diff --git a/internal/command/twofactorverify/twofactorverify_test.go b/internal/command/twofactorverify/twofactorverify_test.go
--- a/internal/command/twofactorverify/twofactorverify_test.go
+++ b/internal/command/twofactorverify/twofactorverify_test.go
@@ -10,11 +10,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
 )
 
 func setup(t *testing.T) []testserver.TestRequestHandler {
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -7,9 +7,9 @@
 
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/handler"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
diff --git a/internal/command/uploadarchive/gitalycall_test.go b/internal/command/uploadarchive/gitalycall_test.go
--- a/internal/command/uploadarchive/gitalycall_test.go
+++ b/internal/command/uploadarchive/gitalycall_test.go
@@ -8,12 +8,12 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestUploadArchive(t *testing.T) {
diff --git a/internal/command/uploadarchive/uploadarchive.go b/internal/command/uploadarchive/uploadarchive.go
--- a/internal/command/uploadarchive/uploadarchive.go
+++ b/internal/command/uploadarchive/uploadarchive.go
@@ -3,11 +3,11 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 type Command struct {
diff --git a/internal/command/uploadarchive/uploadarchive_test.go b/internal/command/uploadarchive/uploadarchive_test.go
--- a/internal/command/uploadarchive/uploadarchive_test.go
+++ b/internal/command/uploadarchive/uploadarchive_test.go
@@ -7,11 +7,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestForbiddenAccess(t *testing.T) {
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -7,9 +7,9 @@
 
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/handler"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -8,12 +8,12 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestUploadPack(t *testing.T) {
diff --git a/internal/command/uploadpack/uploadpack.go b/internal/command/uploadpack/uploadpack.go
--- a/internal/command/uploadpack/uploadpack.go
+++ b/internal/command/uploadpack/uploadpack.go
@@ -3,12 +3,12 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/customaction"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/customaction"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 type Command struct {
diff --git a/internal/command/uploadpack/uploadpack_test.go b/internal/command/uploadpack/uploadpack_test.go
--- a/internal/command/uploadpack/uploadpack_test.go
+++ b/internal/command/uploadpack/uploadpack_test.go
@@ -7,11 +7,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestForbiddenAccess(t *testing.T) {
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -11,9 +11,9 @@
 
 	yaml "gopkg.in/yaml.v2"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitaly"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 const (
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -9,8 +9,8 @@
 	"github.com/stretchr/testify/require"
 	yaml "gopkg.in/yaml.v2"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 func TestConfigApplyGlobalState(t *testing.T) {
diff --git a/internal/executable/executable_test.go b/internal/executable/executable_test.go
--- a/internal/executable/executable_test.go
+++ b/internal/executable/executable_test.go
@@ -4,7 +4,7 @@
 	"errors"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 
 	"github.com/stretchr/testify/require"
 )
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
--- a/internal/gitaly/gitaly.go
+++ b/internal/gitaly/gitaly.go
@@ -17,7 +17,7 @@
 	"gitlab.com/gitlab-org/labkit/log"
 	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 type Command struct {
diff --git a/internal/gitaly/gitaly_test.go b/internal/gitaly/gitaly_test.go
--- a/internal/gitaly/gitaly_test.go
+++ b/internal/gitaly/gitaly_test.go
@@ -7,7 +7,7 @@
 	"github.com/prometheus/client_golang/prometheus/testutil"
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 func TestPrometheusMetrics(t *testing.T) {
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -6,10 +6,10 @@
 	"net/http"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 const (
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -11,11 +11,11 @@
 
 	"github.com/stretchr/testify/require"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 var (
diff --git a/internal/gitlabnet/authorizedkeys/client.go b/internal/gitlabnet/authorizedkeys/client.go
--- a/internal/gitlabnet/authorizedkeys/client.go
+++ b/internal/gitlabnet/authorizedkeys/client.go
@@ -5,9 +5,9 @@
 	"fmt"
 	"net/url"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 const (
diff --git a/internal/gitlabnet/authorizedkeys/client_test.go b/internal/gitlabnet/authorizedkeys/client_test.go
--- a/internal/gitlabnet/authorizedkeys/client_test.go
+++ b/internal/gitlabnet/authorizedkeys/client_test.go
@@ -7,9 +7,9 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/gitlabnet/client.go b/internal/gitlabnet/client.go
--- a/internal/gitlabnet/client.go
+++ b/internal/gitlabnet/client.go
@@ -6,9 +6,9 @@
 	"net"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/gitlabnet/discover/client.go b/internal/gitlabnet/discover/client.go
--- a/internal/gitlabnet/discover/client.go
+++ b/internal/gitlabnet/discover/client.go
@@ -6,10 +6,10 @@
 	"net/http"
 	"net/url"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/discover/client_test.go b/internal/gitlabnet/discover/client_test.go
--- a/internal/gitlabnet/discover/client_test.go
+++ b/internal/gitlabnet/discover/client_test.go
@@ -8,11 +8,11 @@
 	"net/url"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/gitlabnet/healthcheck/client.go b/internal/gitlabnet/healthcheck/client.go
--- a/internal/gitlabnet/healthcheck/client.go
+++ b/internal/gitlabnet/healthcheck/client.go
@@ -5,9 +5,9 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 const (
diff --git a/internal/gitlabnet/healthcheck/client_test.go b/internal/gitlabnet/healthcheck/client_test.go
--- a/internal/gitlabnet/healthcheck/client_test.go
+++ b/internal/gitlabnet/healthcheck/client_test.go
@@ -6,8 +6,8 @@
 	"net/http"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 
 	"github.com/stretchr/testify/require"
 )
diff --git a/internal/gitlabnet/lfsauthenticate/client.go b/internal/gitlabnet/lfsauthenticate/client.go
--- a/internal/gitlabnet/lfsauthenticate/client.go
+++ b/internal/gitlabnet/lfsauthenticate/client.go
@@ -6,10 +6,10 @@
 	"net/http"
 	"strings"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/lfsauthenticate/client_test.go b/internal/gitlabnet/lfsauthenticate/client_test.go
--- a/internal/gitlabnet/lfsauthenticate/client_test.go
+++ b/internal/gitlabnet/lfsauthenticate/client_test.go
@@ -9,9 +9,9 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 const (
diff --git a/internal/gitlabnet/personalaccesstoken/client.go b/internal/gitlabnet/personalaccesstoken/client.go
--- a/internal/gitlabnet/personalaccesstoken/client.go
+++ b/internal/gitlabnet/personalaccesstoken/client.go
@@ -6,11 +6,11 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/personalaccesstoken/client_test.go b/internal/gitlabnet/personalaccesstoken/client_test.go
--- a/internal/gitlabnet/personalaccesstoken/client_test.go
+++ b/internal/gitlabnet/personalaccesstoken/client_test.go
@@ -8,11 +8,11 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 var (
diff --git a/internal/gitlabnet/twofactorrecover/client.go b/internal/gitlabnet/twofactorrecover/client.go
--- a/internal/gitlabnet/twofactorrecover/client.go
+++ b/internal/gitlabnet/twofactorrecover/client.go
@@ -6,11 +6,11 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/twofactorrecover/client_test.go b/internal/gitlabnet/twofactorrecover/client_test.go
--- a/internal/gitlabnet/twofactorrecover/client_test.go
+++ b/internal/gitlabnet/twofactorrecover/client_test.go
@@ -8,11 +8,11 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 var (
diff --git a/internal/gitlabnet/twofactorverify/client.go b/internal/gitlabnet/twofactorverify/client.go
--- a/internal/gitlabnet/twofactorverify/client.go
+++ b/internal/gitlabnet/twofactorverify/client.go
@@ -6,11 +6,11 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/twofactorverify/client_test.go b/internal/gitlabnet/twofactorverify/client_test.go
--- a/internal/gitlabnet/twofactorverify/client_test.go
+++ b/internal/gitlabnet/twofactorverify/client_test.go
@@ -7,13 +7,13 @@
 	"net/http"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func initialize(t *testing.T) []testserver.TestRequestHandler {
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -11,10 +11,10 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitaly"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/labkit/log"
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -12,10 +12,10 @@
 	grpcstatus "google.golang.org/grpc/status"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn) (int32, error) {
diff --git a/internal/keyline/key_line.go b/internal/keyline/key_line.go
--- a/internal/keyline/key_line.go
+++ b/internal/keyline/key_line.go
@@ -7,8 +7,8 @@
 	"regexp"
 	"strings"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
 )
 
 var (
diff --git a/internal/keyline/key_line_test.go b/internal/keyline/key_line_test.go
--- a/internal/keyline/key_line_test.go
+++ b/internal/keyline/key_line_test.go
@@ -4,7 +4,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func TestFailingNewPublicKeyLine(t *testing.T) {
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -9,7 +9,7 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func logFmt(inFmt string) string {
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -8,7 +8,7 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func TestConfigure(t *testing.T) {
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -13,10 +13,10 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/log"
 )
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -14,10 +14,10 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 type rejectCall struct {
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -10,8 +10,8 @@
 
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/authorizedkeys"
 
 	"gitlab.com/gitlab-org/labkit/log"
 )
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
--- a/internal/sshd/server_config_test.go
+++ b/internal/sshd/server_config_test.go
@@ -12,8 +12,8 @@
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 func TestNewServerConfigWithoutHosts(t *testing.T) {
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -12,13 +12,13 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
-	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 type session struct {
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -11,9 +11,9 @@
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
 )
 
 type fakeChannel struct {
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -12,10 +12,10 @@
 	"github.com/pires/go-proxyproto"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/log"
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -15,9 +15,9 @@
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 const (
diff --git a/internal/sshenv/sshenv_test.go b/internal/sshenv/sshenv_test.go
--- a/internal/sshenv/sshenv_test.go
+++ b/internal/sshenv/sshenv_test.go
@@ -4,7 +4,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 func TestNewFromEnv(t *testing.T) {
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -7,7 +7,7 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
 )
 
 func BuildDisallowedByApiHandlers(t *testing.T) []testserver.TestRequestHandler {
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1657013300 -7200
#      Tue Jul 05 11:28:20 2022 +0200
# Node ID bed6fee616ab7f5565041e304c98f7fa7a4b8e97
# Parent  7fc6cf9719ad6dee23d206e5d3152971d1c1525c
Release v14.8.0

- go: Bump major version to v14 !666
- Pass original IP from PROXY requests to internal API calls !665
- Fix make install copying the wrong binaries !664
- gitlab-sshd: Add support for configuring host certificates !661

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,10 @@
+v14.8.0
+
+- go: Bump major version to v14 !666
+- Pass original IP from PROXY requests to internal API calls !665
+- Fix make install copying the wrong binaries !664
+- gitlab-sshd: Add support for configuring host certificates !661
+
 v14.7.4
 
 - gitlab-sshd: Update crypto module to fix RSA keys with old gpg-agent !662
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.7.4
+14.8.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1657013923 0
#      Tue Jul 05 09:38:43 2022 +0000
# Node ID 8bb84a6ad6dd3ed5e3ff025513b3604cf83b9e3b
# Parent  7fc6cf9719ad6dee23d206e5d3152971d1c1525c
# Parent  bed6fee616ab7f5565041e304c98f7fa7a4b8e97
Merge branch 'id-release-14-8' into 'main'

Release v14.8.0

See merge request gitlab-org/gitlab-shell!667

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,10 @@
+v14.8.0
+
+- go: Bump major version to v14 !666
+- Pass original IP from PROXY requests to internal API calls !665
+- Fix make install copying the wrong binaries !664
+- gitlab-sshd: Add support for configuring host certificates !661
+
 v14.7.4
 
 - gitlab-sshd: Update crypto module to fix RSA keys with old gpg-agent !662
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.7.4
+14.8.0
# HG changeset patch
# User Alejandro Rodríguez <alejorro70@gmail.com>
# Date 1657734467 -7200
#      Wed Jul 13 19:47:47 2022 +0200
# Node ID 71b98021ef29ebd9a3d7800a490e87189f68d043
# Parent  8bb84a6ad6dd3ed5e3ff025513b3604cf83b9e3b
Update LabKit library to v1.16.0

* include original address in correlation CIDR checks ([ae96001](https://gitlab.com/gitlab-org/labkit/commit/ae9600163a6f5fa2ad06676a00b310af36573df4))
* run make recipes in parallel during backward compat check ([efa9c71](https://gitlab.com/gitlab-org/labkit/commit/efa9c71e13ef2bfe4415278e6b1e5c5ee8cc8022))

See https://gitlab.com/gitlab-org/labkit/-/releases/v1.16.0

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,7 @@
 	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
-	gitlab.com/gitlab-org/labkit v1.14.0
+	gitlab.com/gitlab-org/labkit v1.16.0
 	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/grpc v1.40.0
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -887,6 +887,7 @@
 gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059 h1:X7+3GQIxUpScXpIMCU5+sfpYvZyBIQ3GMlEosP7Jssw=
 gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
+gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2 h1:zz/4sD22gZqvAdBgd6gYLRIbvrgoQLDE7emPK0sXC6o=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed h1:aXSyBpG6K/QsTGevZnpFoDR7Nwvn24RpkDoWe37B8eY=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -895,8 +896,8 @@
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
 gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
 gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
-gitlab.com/gitlab-org/labkit v1.14.0 h1:LSrvHgybidPyH8fHnsy1GBghrLR4kFObFrtZwUfCgAI=
-gitlab.com/gitlab-org/labkit v1.14.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
+gitlab.com/gitlab-org/labkit v1.16.0 h1:Vm3NAMZ8RqAunXlvPWby3GJ2R35vsYGP6Uu0YjyMIlY=
+gitlab.com/gitlab-org/labkit v1.16.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1657749660 0
#      Wed Jul 13 22:01:00 2022 +0000
# Node ID e86c7d77a163f0d1926b6ee63c9e5e8b1dc55e19
# Parent  8bb84a6ad6dd3ed5e3ff025513b3604cf83b9e3b
# Parent  71b98021ef29ebd9a3d7800a490e87189f68d043
Merge branch 'vendor-v1.16.0' into 'main'

Update LabKit library to v1.16.0

See merge request gitlab-org/gitlab-shell!668

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,7 @@
 	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
-	gitlab.com/gitlab-org/labkit v1.14.0
+	gitlab.com/gitlab-org/labkit v1.16.0
 	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/grpc v1.40.0
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -887,6 +887,7 @@
 gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059 h1:X7+3GQIxUpScXpIMCU5+sfpYvZyBIQ3GMlEosP7Jssw=
 gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
+gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2 h1:zz/4sD22gZqvAdBgd6gYLRIbvrgoQLDE7emPK0sXC6o=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed h1:aXSyBpG6K/QsTGevZnpFoDR7Nwvn24RpkDoWe37B8eY=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -895,8 +896,8 @@
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
 gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
 gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
-gitlab.com/gitlab-org/labkit v1.14.0 h1:LSrvHgybidPyH8fHnsy1GBghrLR4kFObFrtZwUfCgAI=
-gitlab.com/gitlab-org/labkit v1.14.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
+gitlab.com/gitlab-org/labkit v1.16.0 h1:Vm3NAMZ8RqAunXlvPWby3GJ2R35vsYGP6Uu0YjyMIlY=
+gitlab.com/gitlab-org/labkit v1.16.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1657830895 25200
#      Thu Jul 14 13:34:55 2022 -0700
# Node ID b584562b76540fbfc054a976a2ae46e2ef39f77c
# Parent  e86c7d77a163f0d1926b6ee63c9e5e8b1dc55e19
Release v14.9.0

- Update LabKit library to v1.16.0 !668
  (https://gitlab.com/gitlab-org/labkit/-/releases/v1.16.0)

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.9.0
+
+- Update LabKit library to v1.16.0 !668
+
 v14.8.0
 
 - go: Bump major version to v14 !666
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.8.0
+14.9.0
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1657861769 0
#      Fri Jul 15 05:09:29 2022 +0000
# Node ID 00502e780098673e6e130e33b80b04710dca7ad5
# Parent  e86c7d77a163f0d1926b6ee63c9e5e8b1dc55e19
# Parent  b584562b76540fbfc054a976a2ae46e2ef39f77c
Merge branch 'sh-release-14.9.0' into 'main'

Release v14.9.0

See merge request gitlab-org/gitlab-shell!669

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.9.0
+
+- Update LabKit library to v1.16.0 !668
+
 v14.8.0
 
 - go: Bump major version to v14 !666
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.8.0
+14.9.0
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1657861711 25200
#      Thu Jul 14 22:08:31 2022 -0700
# Node ID 3c492987c9aa2edeb461f99e211d77241a127718
# Parent  e86c7d77a163f0d1926b6ee63c9e5e8b1dc55e19
Fix flaky race test

`ignoredError.err` was being used in a Goroutine handler, but the
value of `ignoredError` changes with each test case. To avoid a race,
make a local copy of the error before each Goroutine runs.

Closes https://gitlab.com/gitlab-org/gitlab-shell/-/issues/590

diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -225,9 +225,10 @@
 	} {
 		t.Run(ignoredError.desc, func(t *testing.T) {
 			conn, chans = setup(1, newChannel)
+			ignored := ignoredError.err
 			conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 				close(chans)
-				return ignoredError.err
+				return ignored
 			})
 
 			require.InDelta(t, initialSessionsTotal+2+float64(i), testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1657862126 0
#      Fri Jul 15 05:15:26 2022 +0000
# Node ID 7517f48a5d24f1415f58523c3e2ee1ec72bd437a
# Parent  00502e780098673e6e130e33b80b04710dca7ad5
# Parent  3c492987c9aa2edeb461f99e211d77241a127718
Merge branch 'sh-fix-flaky-race-test' into 'main'

Fix flaky race test

Closes #590

See merge request gitlab-org/gitlab-shell!670

diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -225,9 +225,10 @@
 	} {
 		t.Run(ignoredError.desc, func(t *testing.T) {
 			conn, chans = setup(1, newChannel)
+			ignored := ignoredError.err
 			conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 				close(chans)
-				return ignoredError.err
+				return ignored
 			})
 
 			require.InDelta(t, initialSessionsTotal+2+float64(i), testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
# HG changeset patch
# User kmcknight <kmcknight@gitlab.com>
# Date 1614302245 28800
#      Thu Feb 25 17:17:25 2021 -0800
# Node ID fcbdf9e966ae00e60daa009adfba0ee3ecc70a5d
# Parent  7517f48a5d24f1415f58523c3e2ee1ec72bd437a
Implement Push Auth support for 2FA verification

When `2fa_verify` command is executed:

- A user is asked to enter OTP
- A blocking call for push auth is performed

Then:

- If the push auth request fails, the user is still able to enter
OTP
- If OTP is invalid, the `2fa_verify` command ends the execution
- If OTP is valid or push auth request succeeded, then the user is
successfully authenticated
- If 30 seconds passed while no OTP or Push have been provided,
then the `2fa_verify` command ends the execution

diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -4,6 +4,7 @@
 	"context"
 	"fmt"
 	"io"
+	"time"
 
 	"gitlab.com/gitlab-org/labkit/log"
 
@@ -15,23 +16,96 @@
 
 type Command struct {
 	Config     *config.Config
+	Client     *twofactorverify.Client
 	Args       *commandargs.Shell
 	ReadWriter *readwriter.ReadWriter
 }
 
+type Result struct {
+	Error   error
+	Status  string
+	Success bool
+}
+
 func (c *Command) Execute(ctx context.Context) error {
 	ctxlog := log.ContextLogger(ctx)
-	ctxlog.Info("twofactorverify: execute: waiting for user input")
-	otp := c.getOTP(ctx)
 
-	ctxlog.Info("twofactorverify: execute: verifying entered OTP")
-	err := c.verifyOTP(ctx, otp)
+	// config.GetHTTPClient isn't thread-safe so save Client in struct for concurrency
+	// workaround until #518 is fixed
+	var err error
+	c.Client, err = twofactorverify.NewClient(c.Config)
+
 	if err != nil {
 		ctxlog.WithError(err).Error("twofactorverify: execute: OTP verification failed")
 		return err
 	}
 
-	ctxlog.WithError(err).Info("twofactorverify: execute: OTP verified")
+	// Create timeout context
+	// TODO: make timeout configurable
+	const ctxTimeout = 30
+	timeoutCtx, cancelTimeout := context.WithTimeout(ctx, ctxTimeout*time.Second)
+	defer cancelTimeout()
+
+	// Background push notification with timeout
+	pushauth := make(chan Result)
+	go func() {
+		defer close(pushauth)
+		ctxlog.Info("twofactorverify: execute: waiting for push auth")
+		status, success, err := c.pushAuth(timeoutCtx)
+		ctxlog.WithError(err).Info("twofactorverify: execute: push auth verified")
+
+		select {
+		case <-timeoutCtx.Done(): // push cancelled by manual OTP
+			// skip writing to channel
+			ctxlog.Info("twofactorverify: execute: push auth cancelled")
+		default:
+			pushauth <- Result{Error: err, Status: status, Success: success}
+		}
+	}()
+
+	// Also allow manual OTP entry while waiting for push, with same timeout as push
+	verify := make(chan Result)
+	go func() {
+		defer close(verify)
+		ctxlog.Info("twofactorverify: execute: waiting for user input")
+		answer := ""
+		answer = c.getOTP(timeoutCtx)
+		ctxlog.Info("twofactorverify: execute: user input received")
+
+		select {
+		case <-timeoutCtx.Done(): // manual OTP cancelled by push
+			// skip writing to channel
+			ctxlog.Info("twofactorverify: execute: verify cancelled")
+		default:
+			ctxlog.Info("twofactorverify: execute: verifying entered OTP")
+			status, success, err := c.verifyOTP(timeoutCtx, answer)
+			ctxlog.WithError(err).Info("twofactorverify: execute: OTP verified")
+			verify <- Result{Error: err, Status: status, Success: success}
+		}
+	}()
+
+	for {
+		select {
+		case res := <-verify:
+			if res.Status == "" {
+				// channel closed; don't print anything
+			} else {
+				fmt.Fprint(c.ReadWriter.Out, res.Status)
+				return nil
+			}
+		case res := <-pushauth:
+			if res.Status == "" {
+				// channel closed; don't print anything
+			} else {
+				fmt.Fprint(c.ReadWriter.Out, res.Status)
+				return nil
+			}
+		case <-timeoutCtx.Done(): // push timed out
+			fmt.Fprint(c.ReadWriter.Out, "\nOTP verification timed out\n")
+			return nil
+		}
+	}
+
 	return nil
 }
 
@@ -49,18 +123,38 @@
 	return answer
 }
 
-func (c *Command) verifyOTP(ctx context.Context, otp string) error {
-	client, err := twofactorverify.NewClient(c.Config)
-	if err != nil {
-		return err
+func (c *Command) verifyOTP(ctx context.Context, otp string) (status string, success bool, err error) {
+	reason := ""
+
+	success, reason, err = c.Client.VerifyOTP(ctx, c.Args, otp)
+	if success {
+		status = fmt.Sprintf("\nOTP validation successful. Git operations are now allowed.\n")
+	} else {
+		if err != nil {
+			status = fmt.Sprintf("\nOTP validation failed.\n%v\n", err)
+		} else {
+			status = fmt.Sprintf("\nOTP validation failed.\n%v\n", reason)
+		}
 	}
 
-	err = client.VerifyOTP(ctx, c.Args, otp)
-	if err == nil {
-		fmt.Fprint(c.ReadWriter.Out, "\nOTP validation successful. Git operations are now allowed.\n")
+	err = nil
+
+	return
+}
+
+func (c *Command) pushAuth(ctx context.Context) (status string, success bool, err error) {
+	reason := ""
+
+	success, reason, err = c.Client.PushAuth(ctx, c.Args)
+	if success {
+		status = fmt.Sprintf("\nPush OTP validation successful. Git operations are now allowed.\n")
 	} else {
-		fmt.Fprintf(c.ReadWriter.Out, "\nOTP validation failed.\n%v\n", err)
+		if err != nil {
+			status = fmt.Sprintf("\nPush OTP validation failed.\n%v\n", err)
+		} else {
+			status = fmt.Sprintf("\nPush OTP validation failed.\n%v\n", reason)
+		}
 	}
 
-	return nil
+	return
 }
diff --git a/internal/command/twofactorverify/twofactorverify_test.go b/internal/command/twofactorverify/twofactorverify_test.go
deleted file mode 100644
--- a/internal/command/twofactorverify/twofactorverify_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package twofactorverify
-
-import (
-	"bytes"
-	"context"
-	"encoding/json"
-	"io"
-	"net/http"
-	"testing"
-
-	"github.com/stretchr/testify/require"
-
-	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
-)
-
-func setup(t *testing.T) []testserver.TestRequestHandler {
-	requests := []testserver.TestRequestHandler{
-		{
-			Path: "/api/v4/internal/two_factor_otp_check",
-			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := io.ReadAll(r.Body)
-				defer r.Body.Close()
-
-				require.NoError(t, err)
-
-				var requestBody *twofactorverify.RequestBody
-				require.NoError(t, json.Unmarshal(b, &requestBody))
-
-				switch requestBody.KeyId {
-				case "1":
-					body := map[string]interface{}{
-						"success": true,
-					}
-					json.NewEncoder(w).Encode(body)
-				case "error":
-					body := map[string]interface{}{
-						"success": false,
-						"message": "error message",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "broken":
-					w.WriteHeader(http.StatusInternalServerError)
-				}
-			},
-		},
-	}
-
-	return requests
-}
-
-const (
-	question    = "OTP: \n"
-	errorHeader = "OTP validation failed.\n"
-)
-
-func TestExecute(t *testing.T) {
-	requests := setup(t)
-
-	url := testserver.StartSocketHttpServer(t, requests)
-
-	testCases := []struct {
-		desc           string
-		arguments      *commandargs.Shell
-		answer         string
-		expectedOutput string
-	}{
-		{
-			desc:      "With a known key id",
-			arguments: &commandargs.Shell{GitlabKeyId: "1"},
-			answer:    "123456\n",
-			expectedOutput: question +
-				"OTP validation successful. Git operations are now allowed.\n",
-		},
-		{
-			desc:           "With bad response",
-			arguments:      &commandargs.Shell{GitlabKeyId: "-1"},
-			answer:         "123456\n",
-			expectedOutput: question + errorHeader + "Parsing failed\n",
-		},
-		{
-			desc:           "With API returns an error",
-			arguments:      &commandargs.Shell{GitlabKeyId: "error"},
-			answer:         "yes\n",
-			expectedOutput: question + errorHeader + "error message\n",
-		},
-		{
-			desc:           "With API fails",
-			arguments:      &commandargs.Shell{GitlabKeyId: "broken"},
-			answer:         "yes\n",
-			expectedOutput: question + errorHeader + "Internal API error (500)\n",
-		},
-		{
-			desc:           "With missing arguments",
-			arguments:      &commandargs.Shell{},
-			answer:         "yes\n",
-			expectedOutput: question + errorHeader + "who='' is invalid\n",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			output := &bytes.Buffer{}
-			input := bytes.NewBufferString(tc.answer)
-
-			cmd := &Command{
-				Config:     &config.Config{GitlabUrl: url},
-				Args:       tc.arguments,
-				ReadWriter: &readwriter.ReadWriter{Out: output, In: input},
-			}
-
-			err := cmd.Execute(context.Background())
-
-			require.NoError(t, err)
-			require.Equal(t, tc.expectedOutput, output.String())
-		})
-	}
-}
diff --git a/internal/command/twofactorverify/twofactorverifymanual_test.go b/internal/command/twofactorverify/twofactorverifymanual_test.go
new file mode 100644
--- /dev/null
+++ b/internal/command/twofactorverify/twofactorverifymanual_test.go
@@ -0,0 +1,154 @@
+package twofactorverify
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"io"
+	"net/http"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
+)
+
+func setupManual(t *testing.T) []testserver.TestRequestHandler {
+	requests := []testserver.TestRequestHandler{
+		{
+			Path: "/api/v4/internal/two_factor_manual_otp_check",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				b, err := io.ReadAll(r.Body)
+				defer r.Body.Close()
+
+				require.NoError(t, err)
+
+				var requestBody *twofactorverify.RequestBody
+				require.NoError(t, json.Unmarshal(b, &requestBody))
+
+				var body map[string]interface{}
+				switch requestBody.KeyId {
+				case "1":
+					body = map[string]interface{}{
+						"success": true,
+					}
+					json.NewEncoder(w).Encode(body)
+				case "error":
+					body = map[string]interface{}{
+						"success": false,
+						"message": "error message",
+					}
+					require.NoError(t, json.NewEncoder(w).Encode(body))
+				case "broken":
+					w.WriteHeader(http.StatusInternalServerError)
+				}
+			},
+		},
+		{
+			Path: "/api/v4/internal/two_factor_push_otp_check",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				b, err := io.ReadAll(r.Body)
+				defer r.Body.Close()
+
+				require.NoError(t, err)
+
+				var requestBody *twofactorverify.RequestBody
+				require.NoError(t, json.Unmarshal(b, &requestBody))
+
+				time.Sleep(5 * time.Second)
+
+				var body map[string]interface{}
+				switch requestBody.KeyId {
+				case "1":
+					body = map[string]interface{}{
+						"success": true,
+					}
+					json.NewEncoder(w).Encode(body)
+				case "error":
+					body = map[string]interface{}{
+						"success": false,
+						"message": "error message",
+					}
+					require.NoError(t, json.NewEncoder(w).Encode(body))
+				case "broken":
+					w.WriteHeader(http.StatusInternalServerError)
+				default:
+					body = map[string]interface{}{
+						"success": true,
+						"message": "default message",
+					}
+					json.NewEncoder(w).Encode(body)
+				}
+			},
+		},
+	}
+
+	return requests
+}
+
+const (
+	manualQuestion    = "OTP: \n"
+	manualErrorHeader = "OTP validation failed.\n"
+)
+
+func TestExecuteManual(t *testing.T) {
+	requests := setupManual(t)
+
+	url := testserver.StartSocketHttpServer(t, requests)
+
+	testCases := []struct {
+		desc           string
+		arguments      *commandargs.Shell
+		answer         string
+		expectedOutput string
+	}{
+		{
+			desc:           "With a known key id",
+			arguments:      &commandargs.Shell{GitlabKeyId: "1"},
+			answer:         "123456\n",
+			expectedOutput: manualQuestion + "OTP validation successful. Git operations are now allowed.\n",
+		},
+		{
+			desc:           "With bad response",
+			arguments:      &commandargs.Shell{GitlabKeyId: "-1"},
+			answer:         "123456\n",
+			expectedOutput: manualQuestion + manualErrorHeader + "Parsing failed\n",
+		},
+		{
+			desc:           "With API returns an error",
+			arguments:      &commandargs.Shell{GitlabKeyId: "error"},
+			answer:         "yes\n",
+			expectedOutput: manualQuestion + manualErrorHeader + "error message\n",
+		},
+		{
+			desc:           "With API fails",
+			arguments:      &commandargs.Shell{GitlabKeyId: "broken"},
+			answer:         "yes\n",
+			expectedOutput: manualQuestion + manualErrorHeader + "Internal API error (500)\n",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			output := &bytes.Buffer{}
+
+			input := bytes.NewBufferString(tc.answer)
+
+			cmd := &Command{
+				Config:     &config.Config{GitlabUrl: url},
+				Args:       tc.arguments,
+				ReadWriter: &readwriter.ReadWriter{Out: output, In: input},
+			}
+
+			err := cmd.Execute(context.Background())
+
+			require.NoError(t, err)
+			require.Equal(t, tc.expectedOutput, output.String())
+		})
+	}
+}
diff --git a/internal/command/twofactorverify/twofactorverifypush_test.go b/internal/command/twofactorverify/twofactorverifypush_test.go
new file mode 100644
--- /dev/null
+++ b/internal/command/twofactorverify/twofactorverifypush_test.go
@@ -0,0 +1,144 @@
+package twofactorverify
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"io"
+	"net/http"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
+)
+
+func setupPush(t *testing.T) []testserver.TestRequestHandler {
+	requests := []testserver.TestRequestHandler{
+		{
+			Path: "/api/v4/internal/two_factor_push_otp_check",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				b, err := io.ReadAll(r.Body)
+				defer r.Body.Close()
+
+				require.NoError(t, err)
+
+				var requestBody *twofactorverify.RequestBody
+				require.NoError(t, json.Unmarshal(b, &requestBody))
+
+				var body map[string]interface{}
+				switch requestBody.KeyId {
+				case "1":
+					body = map[string]interface{}{
+						"success": true,
+					}
+					json.NewEncoder(w).Encode(body)
+				case "error":
+					body = map[string]interface{}{
+						"success": false,
+						"message": "error message",
+					}
+					require.NoError(t, json.NewEncoder(w).Encode(body))
+				case "broken":
+					w.WriteHeader(http.StatusInternalServerError)
+				}
+			},
+		},
+		{
+			Path: "/api/v4/internal/two_factor_manual_otp_check",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				b, err := io.ReadAll(r.Body)
+				defer r.Body.Close()
+
+				require.NoError(t, err)
+
+				var requestBody *twofactorverify.RequestBody
+				require.NoError(t, json.Unmarshal(b, &requestBody))
+
+				time.Sleep(20 * time.Second)
+
+				var body map[string]interface{}
+				switch requestBody.KeyId {
+				case "1":
+					body = map[string]interface{}{
+						"success": true,
+					}
+					json.NewEncoder(w).Encode(body)
+				case "error":
+					body = map[string]interface{}{
+						"success": false,
+						"message": "error message",
+					}
+					require.NoError(t, json.NewEncoder(w).Encode(body))
+				case "broken":
+					w.WriteHeader(http.StatusInternalServerError)
+				}
+			},
+		},
+	}
+
+	return requests
+}
+
+const (
+	pushQuestion    = "OTP: \n"
+	pushErrorHeader = "Push OTP validation failed.\n"
+)
+
+func TestExecutePush(t *testing.T) {
+	requests := setupPush(t)
+
+	url := testserver.StartSocketHttpServer(t, requests)
+
+	testCases := []struct {
+		desc           string
+		arguments      *commandargs.Shell
+		answer         string
+		expectedOutput string
+	}{
+		{
+			desc:           "When push is provided",
+			arguments:      &commandargs.Shell{GitlabKeyId: "1"},
+			answer:         "",
+			expectedOutput: pushQuestion + "Push OTP validation successful. Git operations are now allowed.\n",
+		},
+		{
+			desc:           "With API returns an error",
+			arguments:      &commandargs.Shell{GitlabKeyId: "error"},
+			answer:         "",
+			expectedOutput: pushQuestion + pushErrorHeader + "error message\n",
+		},
+		{
+			desc:           "With API fails",
+			arguments:      &commandargs.Shell{GitlabKeyId: "broken"},
+			answer:         "",
+			expectedOutput: pushQuestion + pushErrorHeader + "Internal API error (500)\n",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			output := &bytes.Buffer{}
+
+			var input io.Reader
+			// make input wait for push auth tests
+			input, _ = io.Pipe()
+
+			cmd := &Command{
+				Config:     &config.Config{GitlabUrl: url},
+				Args:       tc.arguments,
+				ReadWriter: &readwriter.ReadWriter{Out: output, In: input},
+			}
+
+			err := cmd.Execute(context.Background())
+
+			require.NoError(t, err)
+			require.Equal(t, tc.expectedOutput, output.String())
+		})
+	}
+}
diff --git a/internal/gitlabnet/twofactorverify/client.go b/internal/gitlabnet/twofactorverify/client.go
--- a/internal/gitlabnet/twofactorverify/client.go
+++ b/internal/gitlabnet/twofactorverify/client.go
@@ -2,7 +2,6 @@
 
 import (
 	"context"
-	"errors"
 	"fmt"
 	"net/http"
 
@@ -38,32 +37,48 @@
 	return &Client{config: config, client: client}, nil
 }
 
-func (c *Client) VerifyOTP(ctx context.Context, args *commandargs.Shell, otp string) error {
+func (c *Client) VerifyOTP(ctx context.Context, args *commandargs.Shell, otp string) (bool, string, error) {
 	requestBody, err := c.getRequestBody(ctx, args, otp)
 	if err != nil {
-		return err
+		return false, "", err
 	}
 
-	response, err := c.client.Post(ctx, "/two_factor_otp_check", requestBody)
+	response, err := c.client.Post(ctx, "/two_factor_manual_otp_check", requestBody)
 	if err != nil {
-		return err
+		return false, "", err
 	}
 	defer response.Body.Close()
 
 	return parse(response)
 }
 
-func parse(hr *http.Response) error {
+func (c *Client) PushAuth(ctx context.Context, args *commandargs.Shell) (bool, string, error) {
+	// enable push auth in internal rest api
+	requestBody, err := c.getRequestBody(ctx, args, "")
+	if err != nil {
+		return false, "", err
+	}
+
+	response, err := c.client.Post(ctx, "/two_factor_push_otp_check", requestBody)
+	if err != nil {
+		return false, "", err
+	}
+	defer response.Body.Close()
+
+	return parse(response)
+}
+
+func parse(hr *http.Response) (bool, string, error) {
 	response := &Response{}
 	if err := gitlabnet.ParseJSON(hr, response); err != nil {
-		return err
+		return false, "", err
 	}
 
 	if !response.Success {
-		return errors.New(response.Message)
+		return false, response.Message, nil
 	}
 
-	return nil
+	return true, response.Message, nil
 }
 
 func (c *Client) getRequestBody(ctx context.Context, args *commandargs.Shell, otp string) (*RequestBody, error) {
diff --git a/internal/gitlabnet/twofactorverify/client_test.go b/internal/gitlabnet/twofactorverify/client_test.go
deleted file mode 100644
--- a/internal/gitlabnet/twofactorverify/client_test.go
+++ /dev/null
@@ -1,151 +0,0 @@
-package twofactorverify
-
-import (
-	"context"
-	"encoding/json"
-	"io"
-	"net/http"
-	"testing"
-
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
-
-	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
-)
-
-func initialize(t *testing.T) []testserver.TestRequestHandler {
-	requests := []testserver.TestRequestHandler{
-		{
-			Path: "/api/v4/internal/two_factor_otp_check",
-			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := io.ReadAll(r.Body)
-				defer r.Body.Close()
-
-				require.NoError(t, err)
-
-				var requestBody *RequestBody
-				require.NoError(t, json.Unmarshal(b, &requestBody))
-
-				switch requestBody.KeyId {
-				case "0":
-					body := map[string]interface{}{
-						"success": true,
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "1":
-					body := map[string]interface{}{
-						"success": false,
-						"message": "error message",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "2":
-					w.WriteHeader(http.StatusForbidden)
-					body := &client.ErrorResponse{
-						Message: "Not allowed!",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "3":
-					w.Write([]byte("{ \"message\": \"broken json!\""))
-				case "4":
-					w.WriteHeader(http.StatusForbidden)
-				}
-
-				if requestBody.UserId == 1 {
-					body := map[string]interface{}{
-						"success": true,
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				}
-			},
-		},
-		{
-			Path: "/api/v4/internal/discover",
-			Handler: func(w http.ResponseWriter, r *http.Request) {
-				body := &discover.Response{
-					UserId:   1,
-					Username: "jane-doe",
-					Name:     "Jane Doe",
-				}
-				require.NoError(t, json.NewEncoder(w).Encode(body))
-			},
-		},
-	}
-
-	return requests
-}
-
-const (
-	otpAttempt = "123456"
-)
-
-func TestVerifyOTPByKeyId(t *testing.T) {
-	client := setup(t)
-
-	args := &commandargs.Shell{GitlabKeyId: "0"}
-	err := client.VerifyOTP(context.Background(), args, otpAttempt)
-	require.NoError(t, err)
-}
-
-func TestVerifyOTPByUsername(t *testing.T) {
-	client := setup(t)
-
-	args := &commandargs.Shell{GitlabUsername: "jane-doe"}
-	err := client.VerifyOTP(context.Background(), args, otpAttempt)
-	require.NoError(t, err)
-}
-
-func TestErrorMessage(t *testing.T) {
-	client := setup(t)
-
-	args := &commandargs.Shell{GitlabKeyId: "1"}
-	err := client.VerifyOTP(context.Background(), args, otpAttempt)
-	require.Equal(t, "error message", err.Error())
-}
-
-func TestErrorResponses(t *testing.T) {
-	client := setup(t)
-
-	testCases := []struct {
-		desc          string
-		fakeId        string
-		expectedError string
-	}{
-		{
-			desc:          "A response with an error message",
-			fakeId:        "2",
-			expectedError: "Not allowed!",
-		},
-		{
-			desc:          "A response with bad JSON",
-			fakeId:        "3",
-			expectedError: "Parsing failed",
-		},
-		{
-			desc:          "An error response without message",
-			fakeId:        "4",
-			expectedError: "Internal API error (403)",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			args := &commandargs.Shell{GitlabKeyId: tc.fakeId}
-			err := client.VerifyOTP(context.Background(), args, otpAttempt)
-
-			require.EqualError(t, err, tc.expectedError)
-		})
-	}
-}
-
-func setup(t *testing.T) *Client {
-	requests := initialize(t)
-	url := testserver.StartSocketHttpServer(t, requests)
-
-	client, err := NewClient(&config.Config{GitlabUrl: url})
-	require.NoError(t, err)
-
-	return client
-}
diff --git a/internal/gitlabnet/twofactorverify/clientmanual_test.go b/internal/gitlabnet/twofactorverify/clientmanual_test.go
new file mode 100644
--- /dev/null
+++ b/internal/gitlabnet/twofactorverify/clientmanual_test.go
@@ -0,0 +1,151 @@
+package twofactorverify
+
+import (
+	"context"
+	"encoding/json"
+	"io"
+	"net/http"
+	"testing"
+
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
+
+	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+)
+
+func initializeManual(t *testing.T) []testserver.TestRequestHandler {
+	requests := []testserver.TestRequestHandler{
+		{
+			Path: "/api/v4/internal/two_factor_manual_otp_check",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				b, err := io.ReadAll(r.Body)
+				defer r.Body.Close()
+
+				require.NoError(t, err)
+
+				var requestBody *RequestBody
+				require.NoError(t, json.Unmarshal(b, &requestBody))
+
+				switch requestBody.KeyId {
+				case "0":
+					body := map[string]interface{}{
+						"success": true,
+					}
+					require.NoError(t, json.NewEncoder(w).Encode(body))
+				case "1":
+					body := map[string]interface{}{
+						"success": false,
+						"message": "error message",
+					}
+					require.NoError(t, json.NewEncoder(w).Encode(body))
+				case "2":
+					w.WriteHeader(http.StatusForbidden)
+					body := &client.ErrorResponse{
+						Message: "Not allowed!",
+					}
+					require.NoError(t, json.NewEncoder(w).Encode(body))
+				case "3":
+					w.Write([]byte("{ \"message\": \"broken json!\""))
+				case "4":
+					w.WriteHeader(http.StatusForbidden)
+				}
+
+				if requestBody.UserId == 1 {
+					body := map[string]interface{}{
+						"success": true,
+					}
+					require.NoError(t, json.NewEncoder(w).Encode(body))
+				}
+			},
+		},
+		{
+			Path: "/api/v4/internal/discover",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				body := &discover.Response{
+					UserId:   1,
+					Username: "jane-doe",
+					Name:     "Jane Doe",
+				}
+				require.NoError(t, json.NewEncoder(w).Encode(body))
+			},
+		},
+	}
+
+	return requests
+}
+
+const (
+	manualOtpAttempt = "123456"
+)
+
+func TestVerifyOTPByKeyId(t *testing.T) {
+	client := setupManual(t)
+
+	args := &commandargs.Shell{GitlabKeyId: "0"}
+	_, _, err := client.VerifyOTP(context.Background(), args, manualOtpAttempt)
+	require.NoError(t, err)
+}
+
+func TestVerifyOTPByUsername(t *testing.T) {
+	client := setupManual(t)
+
+	args := &commandargs.Shell{GitlabUsername: "jane-doe"}
+	_, _, err := client.VerifyOTP(context.Background(), args, manualOtpAttempt)
+	require.NoError(t, err)
+}
+
+func TestErrorMessage(t *testing.T) {
+	client := setupManual(t)
+
+	args := &commandargs.Shell{GitlabKeyId: "1"}
+	_, reason, _ := client.VerifyOTP(context.Background(), args, manualOtpAttempt)
+	require.Equal(t, "error message", reason)
+}
+
+func TestErrorResponses(t *testing.T) {
+	client := setupManual(t)
+
+	testCases := []struct {
+		desc          string
+		fakeId        string
+		expectedError string
+	}{
+		{
+			desc:          "A response with an error message",
+			fakeId:        "2",
+			expectedError: "Not allowed!",
+		},
+		{
+			desc:          "A response with bad JSON",
+			fakeId:        "3",
+			expectedError: "Parsing failed",
+		},
+		{
+			desc:          "An error response without message",
+			fakeId:        "4",
+			expectedError: "Internal API error (403)",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			args := &commandargs.Shell{GitlabKeyId: tc.fakeId}
+			_, _, err := client.VerifyOTP(context.Background(), args, manualOtpAttempt)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
+
+func setupManual(t *testing.T) *Client {
+	requests := initializeManual(t)
+	url := testserver.StartSocketHttpServer(t, requests)
+
+	client, err := NewClient(&config.Config{GitlabUrl: url})
+	require.NoError(t, err)
+
+	return client
+}
diff --git a/internal/gitlabnet/twofactorverify/clientpush_test.go b/internal/gitlabnet/twofactorverify/clientpush_test.go
new file mode 100644
--- /dev/null
+++ b/internal/gitlabnet/twofactorverify/clientpush_test.go
@@ -0,0 +1,139 @@
+package twofactorverify
+
+import (
+	"context"
+	"encoding/json"
+	"io"
+	"net/http"
+	"testing"
+
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
+
+	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+)
+
+func initializePush(t *testing.T) []testserver.TestRequestHandler {
+	requests := []testserver.TestRequestHandler{
+		{
+			Path: "/api/v4/internal/two_factor_push_otp_check",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				b, err := io.ReadAll(r.Body)
+				defer r.Body.Close()
+
+				require.NoError(t, err)
+
+				var requestBody *RequestBody
+				require.NoError(t, json.Unmarshal(b, &requestBody))
+
+				switch requestBody.KeyId {
+				case "0":
+					body := map[string]interface{}{
+						"success": true,
+					}
+					require.NoError(t, json.NewEncoder(w).Encode(body))
+				case "1":
+					body := map[string]interface{}{
+						"success": false,
+						"message": "error message",
+					}
+					require.NoError(t, json.NewEncoder(w).Encode(body))
+				case "2":
+					w.WriteHeader(http.StatusForbidden)
+					body := &client.ErrorResponse{
+						Message: "Not allowed!",
+					}
+					require.NoError(t, json.NewEncoder(w).Encode(body))
+				case "3":
+					w.Write([]byte("{ \"message\": \"broken json!\""))
+				case "4":
+					w.WriteHeader(http.StatusForbidden)
+				}
+
+				if requestBody.UserId == 1 {
+					body := map[string]interface{}{
+						"success": true,
+					}
+					require.NoError(t, json.NewEncoder(w).Encode(body))
+				}
+			},
+		},
+		{
+			Path: "/api/v4/internal/discover",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				body := &discover.Response{
+					UserId:   1,
+					Username: "jane-doe",
+					Name:     "Jane Doe",
+				}
+				require.NoError(t, json.NewEncoder(w).Encode(body))
+			},
+		},
+	}
+
+	return requests
+}
+
+func TestVerifyPush(t *testing.T) {
+	client := setupPush(t)
+
+	args := &commandargs.Shell{GitlabKeyId: "0"}
+	_, _, err := client.PushAuth(context.Background(), args)
+	require.NoError(t, err)
+}
+
+func TestErrorMessagePush(t *testing.T) {
+	client := setupPush(t)
+
+	args := &commandargs.Shell{GitlabKeyId: "1"}
+	_, reason, _ := client.PushAuth(context.Background(), args)
+	require.Equal(t, "error message", reason)
+}
+
+func TestErrorResponsesPush(t *testing.T) {
+	client := setupPush(t)
+
+	testCases := []struct {
+		desc          string
+		fakeId        string
+		expectedError string
+	}{
+		{
+			desc:          "A response with an error message",
+			fakeId:        "2",
+			expectedError: "Not allowed!",
+		},
+		{
+			desc:          "A response with bad JSON",
+			fakeId:        "3",
+			expectedError: "Parsing failed",
+		},
+		{
+			desc:          "An error response without message",
+			fakeId:        "4",
+			expectedError: "Internal API error (403)",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			args := &commandargs.Shell{GitlabKeyId: tc.fakeId}
+			_, _, err := client.PushAuth(context.Background(), args)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
+
+func setupPush(t *testing.T) *Client {
+	requests := initializePush(t)
+	url := testserver.StartSocketHttpServer(t, requests)
+
+	client, err := NewClient(&config.Config{GitlabUrl: url})
+	require.NoError(t, err)
+
+	return client
+}
diff --git a/spec/gitlab_shell_two_factor_manual_verify_spec.rb b/spec/gitlab_shell_two_factor_manual_verify_spec.rb
new file mode 100644
--- /dev/null
+++ b/spec/gitlab_shell_two_factor_manual_verify_spec.rb
@@ -0,0 +1,92 @@
+require_relative 'spec_helper'
+
+require 'open3'
+require 'json'
+
+describe 'bin/gitlab-shell 2fa_verify manual' do
+  include_context 'gitlab shell'
+
+  let(:env) do
+    { 'SSH_CONNECTION' => 'fake',
+      'SSH_ORIGINAL_COMMAND' => '2fa_verify' }
+  end
+
+  before(:context) do
+    write_config('gitlab_url' => "http+unix://#{CGI.escape(tmp_socket_path)}")
+  end
+
+  def mock_server(server)
+    server.mount_proc('/api/v4/internal/two_factor_manual_otp_check') do |req, res|
+      res.content_type = 'application/json'
+      res.status = 200
+
+      params = JSON.parse(req.body)
+      key_id = params['key_id'] || params['user_id'].to_s
+
+      if key_id == '100'
+        res.body = { success: true }.to_json
+      else
+        res.body = { success: false, message: 'boom!' }.to_json
+      end
+    end
+
+    server.mount_proc('/api/v4/internal/two_factor_push_otp_check') do |req, res|
+      res.content_type = 'application/json'
+      res.status = 200
+
+      params = JSON.parse(req.body)
+      key_id = params['key_id'] || params['user_id'].to_s
+
+      sleep 5 # simulate Fortinet API timing
+      res.body = { success: false, message: 'boom!' }.to_json
+    end
+
+    server.mount_proc('/api/v4/internal/discover') do |_, res|
+      res.status = 200
+      res.content_type = 'application/json'
+      res.body = { id: 100, name: 'Some User', username: 'someuser' }.to_json
+    end
+  end
+
+  describe 'command' do
+    context 'when key is provided' do
+      let(:cmd) { "#{gitlab_shell_path} key-100" }
+
+      it 'prints a successful verification message' do
+        verify_successful_verification!(cmd)
+      end
+    end
+
+    context 'when username is provided' do
+      let(:cmd) { "#{gitlab_shell_path} username-someone" }
+
+      it 'prints a successful verification message' do
+        verify_successful_verification!(cmd)
+      end
+    end
+
+    context 'when API error occurs' do
+      let(:cmd) { "#{gitlab_shell_path} key-101" }
+
+      it 'prints the error message' do
+        Open3.popen2(env, cmd) do |stdin, stdout|
+          expect(stdout.gets(5)).to eq('OTP: ')
+
+          stdin.puts('123456')
+
+          expect(stdout.flush.read).to eq("\nOTP validation failed.\nboom!\n")
+        end
+      end
+    end
+  end
+
+  def verify_successful_verification!(cmd)
+    Open3.popen2(env, cmd) do |stdin, stdout|
+      expect(stdout.gets(5)).to eq('OTP: ')
+
+      stdin.puts('123456')
+
+      expect(stdout.flush.read).to eq("\nOTP validation successful. Git operations are now allowed.\n")
+    end
+  end
+end
diff --git a/spec/gitlab_shell_two_factor_push_verify_spec.rb b/spec/gitlab_shell_two_factor_push_verify_spec.rb
new file mode 100644
--- /dev/null
+++ b/spec/gitlab_shell_two_factor_push_verify_spec.rb
@@ -0,0 +1,71 @@
+require_relative 'spec_helper'
+
+require 'open3'
+require 'json'
+
+describe 'bin/gitlab-shell 2fa_verify push' do
+  include_context 'gitlab shell'
+
+  let(:env) do
+    { 'SSH_CONNECTION' => 'fake',
+      'SSH_ORIGINAL_COMMAND' => '2fa_verify' }
+  end
+
+  before(:context) do
+    write_config('gitlab_url' => "http+unix://#{CGI.escape(tmp_socket_path)}")
+  end
+
+  def mock_server(server)
+    server.mount_proc('/api/v4/internal/two_factor_push_otp_check') do |req, res|
+      res.content_type = 'application/json'
+      res.status = 200
+
+      params = JSON.parse(req.body)
+      key_id = params['key_id'] || params['user_id'].to_s
+
+      if key_id == '100'
+        res.body = { success: false }.to_json
+      elsif key_id == '102'
+        res.body = { success: true }.to_json
+      else
+        res.body = { success: false, message: 'boom!' }.to_json
+      end
+    end
+
+    server.mount_proc('/api/v4/internal/discover') do |_, res|
+      res.status = 200
+      res.content_type = 'application/json'
+      res.body = { id: 100, name: 'Some User', username: 'someuser' }.to_json
+    end
+  end
+
+  describe 'command' do
+    context 'when push is provided' do
+      let(:cmd) { "#{gitlab_shell_path} key-102" }
+
+      it 'prints a successful push verification message' do
+        verify_successful_verification_push!(cmd)
+      end
+    end
+
+    context 'when API error occurs' do
+      let(:cmd) { "#{gitlab_shell_path} key-101" }
+
+      it 'prints the error message' do
+        Open3.popen2(env, cmd) do |stdin, stdout|
+          expect(stdout.gets(5)).to eq('OTP: ')
+
+          expect(stdout.flush.read).to eq("\nPush OTP validation failed.\nboom!\n")
+        end
+      end
+    end
+  end
+
+  def verify_successful_verification_push!(cmd)
+    Open3.popen2(env, cmd) do |stdin, stdout|
+      expect(stdout.gets(5)).to eq('OTP: ')
+
+      expect(stdout.flush.read).to eq("\nPush OTP validation successful. Git operations are now allowed.\n")
+    end
+  end
+end
diff --git a/spec/gitlab_shell_two_factor_verify_spec.rb b/spec/gitlab_shell_two_factor_verify_spec.rb
deleted file mode 100644
--- a/spec/gitlab_shell_two_factor_verify_spec.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-require_relative 'spec_helper'
-
-require 'open3'
-require 'json'
-
-describe 'bin/gitlab-shell 2fa_verify' do
-  include_context 'gitlab shell'
-
-  let(:env) do
-    { 'SSH_CONNECTION' => 'fake',
-      'SSH_ORIGINAL_COMMAND' => '2fa_verify' }
-  end
-
-  before(:context) do
-    write_config('gitlab_url' => "http+unix://#{CGI.escape(tmp_socket_path)}")
-  end
-
-  def mock_server(server)
-    server.mount_proc('/api/v4/internal/two_factor_otp_check') do |req, res|
-      res.content_type = 'application/json'
-      res.status = 200
-
-      params = JSON.parse(req.body)
-      key_id = params['key_id'] || params['user_id'].to_s
-
-      if key_id == '100'
-        res.body = { success: true }.to_json
-      else
-        res.body = { success: false, message: 'boom!' }.to_json
-      end
-    end
-
-    server.mount_proc('/api/v4/internal/discover') do |_, res|
-      res.status = 200
-      res.content_type = 'application/json'
-      res.body = { id: 100, name: 'Some User', username: 'someuser' }.to_json
-    end
-  end
-
-  describe 'command' do
-    context 'when key is provided' do
-      let(:cmd) { "#{gitlab_shell_path} key-100" }
-
-      it 'prints a successful verification message' do
-        verify_successful_verification!(cmd)
-      end
-    end
-
-    context 'when username is provided' do
-      let(:cmd) { "#{gitlab_shell_path} username-someone" }
-
-      it 'prints a successful verification message' do
-        verify_successful_verification!(cmd)
-      end
-    end
-
-    context 'when API error occurs' do
-      let(:cmd) { "#{gitlab_shell_path} key-101" }
-
-      it 'prints the error message' do
-        Open3.popen2(env, cmd) do |stdin, stdout|
-          expect(stdout.gets(5)).to eq('OTP: ')
-
-          stdin.puts('123456')
-
-          expect(stdout.flush.read).to eq("\nOTP validation failed.\nboom!\n")
-        end
-      end
-    end
-  end
-
-  def verify_successful_verification!(cmd)
-    Open3.popen2(env, cmd) do |stdin, stdout|
-      expect(stdout.gets(5)).to eq('OTP: ')
-
-      stdin.puts('123456')
-
-      expect(stdout.flush.read).to eq("\nOTP validation successful. Git operations are now allowed.\n")
-    end
-  end
-end
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1657977306 -7200
#      Sat Jul 16 15:15:06 2022 +0200
# Node ID 3b8480785bad7d68dfac81978e90675bfbc81121
# Parent  fcbdf9e966ae00e60daa009adfba0ee3ecc70a5d
Simplify 2FA Push auth processing

Use a single channel to handle both Push Auth and OTP results

diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -14,105 +14,63 @@
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
 )
 
+const (
+	timeout = 30 * time.Second
+	prompt  = "OTP: "
+)
+
 type Command struct {
 	Config     *config.Config
-	Client     *twofactorverify.Client
 	Args       *commandargs.Shell
 	ReadWriter *readwriter.ReadWriter
 }
 
-type Result struct {
-	Error   error
-	Status  string
-	Success bool
-}
-
 func (c *Command) Execute(ctx context.Context) error {
-	ctxlog := log.ContextLogger(ctx)
-
-	// config.GetHTTPClient isn't thread-safe so save Client in struct for concurrency
-	// workaround until #518 is fixed
-	var err error
-	c.Client, err = twofactorverify.NewClient(c.Config)
-
+	client, err := twofactorverify.NewClient(c.Config)
 	if err != nil {
-		ctxlog.WithError(err).Error("twofactorverify: execute: OTP verification failed")
 		return err
 	}
 
-	// Create timeout context
-	// TODO: make timeout configurable
-	const ctxTimeout = 30
-	timeoutCtx, cancelTimeout := context.WithTimeout(ctx, ctxTimeout*time.Second)
-	defer cancelTimeout()
+	ctx, cancel := context.WithTimeout(ctx, timeout)
+	defer cancel()
 
-	// Background push notification with timeout
-	pushauth := make(chan Result)
+	fmt.Fprint(c.ReadWriter.Out, prompt)
+
+	resultCh := make(chan string)
 	go func() {
-		defer close(pushauth)
-		ctxlog.Info("twofactorverify: execute: waiting for push auth")
-		status, success, err := c.pushAuth(timeoutCtx)
-		ctxlog.WithError(err).Info("twofactorverify: execute: push auth verified")
-
-		select {
-		case <-timeoutCtx.Done(): // push cancelled by manual OTP
-			// skip writing to channel
-			ctxlog.Info("twofactorverify: execute: push auth cancelled")
-		default:
-			pushauth <- Result{Error: err, Status: status, Success: success}
+		err := client.PushAuth(ctx, c.Args)
+		if err == nil {
+			resultCh <- "OTP has been validated by Push Authentication. Git operations are now allowed."
 		}
 	}()
 
-	// Also allow manual OTP entry while waiting for push, with same timeout as push
-	verify := make(chan Result)
 	go func() {
-		defer close(verify)
-		ctxlog.Info("twofactorverify: execute: waiting for user input")
-		answer := ""
-		answer = c.getOTP(timeoutCtx)
-		ctxlog.Info("twofactorverify: execute: user input received")
+		answer, err := c.getOTP(ctx)
+		if err != nil {
+			resultCh <- formatErr(err)
+		}
 
-		select {
-		case <-timeoutCtx.Done(): // manual OTP cancelled by push
-			// skip writing to channel
-			ctxlog.Info("twofactorverify: execute: verify cancelled")
-		default:
-			ctxlog.Info("twofactorverify: execute: verifying entered OTP")
-			status, success, err := c.verifyOTP(timeoutCtx, answer)
-			ctxlog.WithError(err).Info("twofactorverify: execute: OTP verified")
-			verify <- Result{Error: err, Status: status, Success: success}
+		if err := client.VerifyOTP(ctx, c.Args, answer); err != nil {
+			resultCh <- formatErr(err)
+		} else {
+			resultCh <- "OTP validation successful. Git operations are now allowed."
 		}
 	}()
 
-	for {
-		select {
-		case res := <-verify:
-			if res.Status == "" {
-				// channel closed; don't print anything
-			} else {
-				fmt.Fprint(c.ReadWriter.Out, res.Status)
-				return nil
-			}
-		case res := <-pushauth:
-			if res.Status == "" {
-				// channel closed; don't print anything
-			} else {
-				fmt.Fprint(c.ReadWriter.Out, res.Status)
-				return nil
-			}
-		case <-timeoutCtx.Done(): // push timed out
-			fmt.Fprint(c.ReadWriter.Out, "\nOTP verification timed out\n")
-			return nil
-		}
+	var message string
+	select {
+	case message = <-resultCh:
+	case <-ctx.Done():
+		message = formatErr(ctx.Err())
 	}
 
+	log.WithContextFields(ctx, log.Fields{"message": message}).Info("Two factor verify command finished")
+	fmt.Fprintf(c.ReadWriter.Out, "\n%v\n", message)
+
 	return nil
 }
 
-func (c *Command) getOTP(ctx context.Context) string {
-	prompt := "OTP: "
-	fmt.Fprint(c.ReadWriter.Out, prompt)
-
+func (c *Command) getOTP(ctx context.Context) (string, error) {
 	var answer string
 	otpLength := int64(64)
 	reader := io.LimitReader(c.ReadWriter.In, otpLength)
@@ -120,41 +78,13 @@
 		log.ContextLogger(ctx).WithError(err).Debug("twofactorverify: getOTP: Failed to get user input")
 	}
 
-	return answer
-}
-
-func (c *Command) verifyOTP(ctx context.Context, otp string) (status string, success bool, err error) {
-	reason := ""
-
-	success, reason, err = c.Client.VerifyOTP(ctx, c.Args, otp)
-	if success {
-		status = fmt.Sprintf("\nOTP validation successful. Git operations are now allowed.\n")
-	} else {
-		if err != nil {
-			status = fmt.Sprintf("\nOTP validation failed.\n%v\n", err)
-		} else {
-			status = fmt.Sprintf("\nOTP validation failed.\n%v\n", reason)
-		}
+	if answer == "" {
+		return "", fmt.Errorf("OTP cannot be blank.")
 	}
 
-	err = nil
-
-	return
+	return answer, nil
 }
 
-func (c *Command) pushAuth(ctx context.Context) (status string, success bool, err error) {
-	reason := ""
-
-	success, reason, err = c.Client.PushAuth(ctx, c.Args)
-	if success {
-		status = fmt.Sprintf("\nPush OTP validation successful. Git operations are now allowed.\n")
-	} else {
-		if err != nil {
-			status = fmt.Sprintf("\nPush OTP validation failed.\n%v\n", err)
-		} else {
-			status = fmt.Sprintf("\nPush OTP validation failed.\n%v\n", reason)
-		}
-	}
-
-	return
+func formatErr(err error) string {
+	return fmt.Sprintf("OTP validation failed: %v", err)
 }
diff --git a/internal/command/twofactorverify/twofactorverify_test.go b/internal/command/twofactorverify/twofactorverify_test.go
new file mode 100644
--- /dev/null
+++ b/internal/command/twofactorverify/twofactorverify_test.go
@@ -0,0 +1,191 @@
+package twofactorverify
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"io"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
+)
+
+type blockingReader struct{}
+
+func (*blockingReader) Read([]byte) (int, error) {
+	waitInfinitely := make(chan struct{})
+	<-waitInfinitely
+
+	return 0, nil
+}
+
+func setup(t *testing.T) []testserver.TestRequestHandler {
+	waitInfinitely := make(chan struct{})
+	requests := []testserver.TestRequestHandler{
+		{
+			Path: "/api/v4/internal/two_factor_manual_otp_check",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				b, err := io.ReadAll(r.Body)
+				defer r.Body.Close()
+
+				require.NoError(t, err)
+
+				var requestBody *twofactorverify.RequestBody
+				require.NoError(t, json.Unmarshal(b, &requestBody))
+
+				switch requestBody.KeyId {
+				case "verify_via_otp", "verify_via_otp_with_push_error":
+					body := map[string]interface{}{
+						"success": true,
+					}
+					json.NewEncoder(w).Encode(body)
+				case "wait_infinitely":
+					<-waitInfinitely
+				case "error":
+					body := map[string]interface{}{
+						"success": false,
+						"message": "error message",
+					}
+					require.NoError(t, json.NewEncoder(w).Encode(body))
+				case "broken":
+					w.WriteHeader(http.StatusInternalServerError)
+				}
+			},
+		},
+		{
+			Path: "/api/v4/internal/two_factor_push_otp_check",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				b, err := io.ReadAll(r.Body)
+				defer r.Body.Close()
+
+				require.NoError(t, err)
+
+				var requestBody *twofactorverify.RequestBody
+				require.NoError(t, json.Unmarshal(b, &requestBody))
+
+				switch requestBody.KeyId {
+				case "verify_via_push":
+					body := map[string]interface{}{
+						"success": true,
+					}
+					json.NewEncoder(w).Encode(body)
+				case "verify_via_otp_with_push_error":
+					w.WriteHeader(http.StatusInternalServerError)
+				default:
+					<-waitInfinitely
+				}
+			},
+		},
+	}
+
+	return requests
+}
+
+const errorHeader = "OTP validation failed: "
+
+func TestExecute(t *testing.T) {
+	requests := setup(t)
+
+	url := testserver.StartSocketHttpServer(t, requests)
+
+	testCases := []struct {
+		desc           string
+		arguments      *commandargs.Shell
+		input          io.Reader
+		expectedOutput string
+	}{
+		{
+			desc:           "Verify via OTP",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_otp"},
+			expectedOutput: "OTP validation successful. Git operations are now allowed.\n",
+		},
+		{
+			desc:           "Verify via OTP",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_otp_with_push_error"},
+			expectedOutput: "OTP validation successful. Git operations are now allowed.\n",
+		},
+		{
+			desc:           "Verify via push authentication",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_push"},
+			input:          &blockingReader{},
+			expectedOutput: "OTP has been validated by Push Authentication. Git operations are now allowed.\n",
+		},
+		{
+			desc:           "With an empty OTP",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_otp"},
+			input:          bytes.NewBufferString("\n"),
+			expectedOutput: errorHeader + "OTP cannot be blank.\n",
+		},
+		{
+			desc:           "With bad response",
+			arguments:      &commandargs.Shell{GitlabKeyId: "-1"},
+			expectedOutput: errorHeader + "Parsing failed\n",
+		},
+		{
+			desc:           "With API returns an error",
+			arguments:      &commandargs.Shell{GitlabKeyId: "error"},
+			expectedOutput: errorHeader + "error message\n",
+		},
+		{
+			desc:           "With API fails",
+			arguments:      &commandargs.Shell{GitlabKeyId: "broken"},
+			expectedOutput: errorHeader + "Internal API error (500)\n",
+		},
+		{
+			desc:           "With missing arguments",
+			arguments:      &commandargs.Shell{},
+			expectedOutput: errorHeader + "who='' is invalid\n",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			output := &bytes.Buffer{}
+
+			input := tc.input
+			if input == nil {
+				input = bytes.NewBufferString("123456\n")
+			}
+
+			cmd := &Command{
+				Config:     &config.Config{GitlabUrl: url},
+				Args:       tc.arguments,
+				ReadWriter: &readwriter.ReadWriter{Out: output, In: input},
+			}
+
+			err := cmd.Execute(context.Background())
+
+			require.NoError(t, err)
+			require.Equal(t, prompt+"\n"+tc.expectedOutput, output.String())
+		})
+	}
+}
+
+func TestCanceledContext(t *testing.T) {
+	requests := setup(t)
+
+	output := &bytes.Buffer{}
+
+	url := testserver.StartSocketHttpServer(t, requests)
+	cmd := &Command{
+		Config:     &config.Config{GitlabUrl: url},
+		Args:       &commandargs.Shell{GitlabKeyId: "wait_infinitely"},
+		ReadWriter: &readwriter.ReadWriter{Out: output, In: &bytes.Buffer{}},
+	}
+
+	ctx, cancel := context.WithCancel(context.Background())
+
+	errCh := make(chan error)
+	go func() { errCh <- cmd.Execute(ctx) }()
+	cancel()
+
+	require.NoError(t, <-errCh)
+	require.Equal(t, prompt+"\n"+errorHeader+"context canceled\n", output.String())
+}
diff --git a/internal/command/twofactorverify/twofactorverifymanual_test.go b/internal/command/twofactorverify/twofactorverifymanual_test.go
deleted file mode 100644
--- a/internal/command/twofactorverify/twofactorverifymanual_test.go
+++ /dev/null
@@ -1,154 +0,0 @@
-package twofactorverify
-
-import (
-	"bytes"
-	"context"
-	"encoding/json"
-	"io"
-	"net/http"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/require"
-
-	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
-)
-
-func setupManual(t *testing.T) []testserver.TestRequestHandler {
-	requests := []testserver.TestRequestHandler{
-		{
-			Path: "/api/v4/internal/two_factor_manual_otp_check",
-			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := io.ReadAll(r.Body)
-				defer r.Body.Close()
-
-				require.NoError(t, err)
-
-				var requestBody *twofactorverify.RequestBody
-				require.NoError(t, json.Unmarshal(b, &requestBody))
-
-				var body map[string]interface{}
-				switch requestBody.KeyId {
-				case "1":
-					body = map[string]interface{}{
-						"success": true,
-					}
-					json.NewEncoder(w).Encode(body)
-				case "error":
-					body = map[string]interface{}{
-						"success": false,
-						"message": "error message",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "broken":
-					w.WriteHeader(http.StatusInternalServerError)
-				}
-			},
-		},
-		{
-			Path: "/api/v4/internal/two_factor_push_otp_check",
-			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := io.ReadAll(r.Body)
-				defer r.Body.Close()
-
-				require.NoError(t, err)
-
-				var requestBody *twofactorverify.RequestBody
-				require.NoError(t, json.Unmarshal(b, &requestBody))
-
-				time.Sleep(5 * time.Second)
-
-				var body map[string]interface{}
-				switch requestBody.KeyId {
-				case "1":
-					body = map[string]interface{}{
-						"success": true,
-					}
-					json.NewEncoder(w).Encode(body)
-				case "error":
-					body = map[string]interface{}{
-						"success": false,
-						"message": "error message",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "broken":
-					w.WriteHeader(http.StatusInternalServerError)
-				default:
-					body = map[string]interface{}{
-						"success": true,
-						"message": "default message",
-					}
-					json.NewEncoder(w).Encode(body)
-				}
-			},
-		},
-	}
-
-	return requests
-}
-
-const (
-	manualQuestion    = "OTP: \n"
-	manualErrorHeader = "OTP validation failed.\n"
-)
-
-func TestExecuteManual(t *testing.T) {
-	requests := setupManual(t)
-
-	url := testserver.StartSocketHttpServer(t, requests)
-
-	testCases := []struct {
-		desc           string
-		arguments      *commandargs.Shell
-		answer         string
-		expectedOutput string
-	}{
-		{
-			desc:           "With a known key id",
-			arguments:      &commandargs.Shell{GitlabKeyId: "1"},
-			answer:         "123456\n",
-			expectedOutput: manualQuestion + "OTP validation successful. Git operations are now allowed.\n",
-		},
-		{
-			desc:           "With bad response",
-			arguments:      &commandargs.Shell{GitlabKeyId: "-1"},
-			answer:         "123456\n",
-			expectedOutput: manualQuestion + manualErrorHeader + "Parsing failed\n",
-		},
-		{
-			desc:           "With API returns an error",
-			arguments:      &commandargs.Shell{GitlabKeyId: "error"},
-			answer:         "yes\n",
-			expectedOutput: manualQuestion + manualErrorHeader + "error message\n",
-		},
-		{
-			desc:           "With API fails",
-			arguments:      &commandargs.Shell{GitlabKeyId: "broken"},
-			answer:         "yes\n",
-			expectedOutput: manualQuestion + manualErrorHeader + "Internal API error (500)\n",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			output := &bytes.Buffer{}
-
-			input := bytes.NewBufferString(tc.answer)
-
-			cmd := &Command{
-				Config:     &config.Config{GitlabUrl: url},
-				Args:       tc.arguments,
-				ReadWriter: &readwriter.ReadWriter{Out: output, In: input},
-			}
-
-			err := cmd.Execute(context.Background())
-
-			require.NoError(t, err)
-			require.Equal(t, tc.expectedOutput, output.String())
-		})
-	}
-}
diff --git a/internal/command/twofactorverify/twofactorverifypush_test.go b/internal/command/twofactorverify/twofactorverifypush_test.go
deleted file mode 100644
--- a/internal/command/twofactorverify/twofactorverifypush_test.go
+++ /dev/null
@@ -1,144 +0,0 @@
-package twofactorverify
-
-import (
-	"bytes"
-	"context"
-	"encoding/json"
-	"io"
-	"net/http"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/require"
-
-	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
-)
-
-func setupPush(t *testing.T) []testserver.TestRequestHandler {
-	requests := []testserver.TestRequestHandler{
-		{
-			Path: "/api/v4/internal/two_factor_push_otp_check",
-			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := io.ReadAll(r.Body)
-				defer r.Body.Close()
-
-				require.NoError(t, err)
-
-				var requestBody *twofactorverify.RequestBody
-				require.NoError(t, json.Unmarshal(b, &requestBody))
-
-				var body map[string]interface{}
-				switch requestBody.KeyId {
-				case "1":
-					body = map[string]interface{}{
-						"success": true,
-					}
-					json.NewEncoder(w).Encode(body)
-				case "error":
-					body = map[string]interface{}{
-						"success": false,
-						"message": "error message",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "broken":
-					w.WriteHeader(http.StatusInternalServerError)
-				}
-			},
-		},
-		{
-			Path: "/api/v4/internal/two_factor_manual_otp_check",
-			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := io.ReadAll(r.Body)
-				defer r.Body.Close()
-
-				require.NoError(t, err)
-
-				var requestBody *twofactorverify.RequestBody
-				require.NoError(t, json.Unmarshal(b, &requestBody))
-
-				time.Sleep(20 * time.Second)
-
-				var body map[string]interface{}
-				switch requestBody.KeyId {
-				case "1":
-					body = map[string]interface{}{
-						"success": true,
-					}
-					json.NewEncoder(w).Encode(body)
-				case "error":
-					body = map[string]interface{}{
-						"success": false,
-						"message": "error message",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "broken":
-					w.WriteHeader(http.StatusInternalServerError)
-				}
-			},
-		},
-	}
-
-	return requests
-}
-
-const (
-	pushQuestion    = "OTP: \n"
-	pushErrorHeader = "Push OTP validation failed.\n"
-)
-
-func TestExecutePush(t *testing.T) {
-	requests := setupPush(t)
-
-	url := testserver.StartSocketHttpServer(t, requests)
-
-	testCases := []struct {
-		desc           string
-		arguments      *commandargs.Shell
-		answer         string
-		expectedOutput string
-	}{
-		{
-			desc:           "When push is provided",
-			arguments:      &commandargs.Shell{GitlabKeyId: "1"},
-			answer:         "",
-			expectedOutput: pushQuestion + "Push OTP validation successful. Git operations are now allowed.\n",
-		},
-		{
-			desc:           "With API returns an error",
-			arguments:      &commandargs.Shell{GitlabKeyId: "error"},
-			answer:         "",
-			expectedOutput: pushQuestion + pushErrorHeader + "error message\n",
-		},
-		{
-			desc:           "With API fails",
-			arguments:      &commandargs.Shell{GitlabKeyId: "broken"},
-			answer:         "",
-			expectedOutput: pushQuestion + pushErrorHeader + "Internal API error (500)\n",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			output := &bytes.Buffer{}
-
-			var input io.Reader
-			// make input wait for push auth tests
-			input, _ = io.Pipe()
-
-			cmd := &Command{
-				Config:     &config.Config{GitlabUrl: url},
-				Args:       tc.arguments,
-				ReadWriter: &readwriter.ReadWriter{Out: output, In: input},
-			}
-
-			err := cmd.Execute(context.Background())
-
-			require.NoError(t, err)
-			require.Equal(t, tc.expectedOutput, output.String())
-		})
-	}
-}
diff --git a/internal/gitlabnet/twofactorverify/client.go b/internal/gitlabnet/twofactorverify/client.go
--- a/internal/gitlabnet/twofactorverify/client.go
+++ b/internal/gitlabnet/twofactorverify/client.go
@@ -2,6 +2,7 @@
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"net/http"
 
@@ -25,7 +26,7 @@
 type RequestBody struct {
 	KeyId      string `json:"key_id,omitempty"`
 	UserId     int64  `json:"user_id,omitempty"`
-	OTPAttempt string `json:"otp_attempt"`
+	OTPAttempt string `json:"otp_attempt,omitempty"`
 }
 
 func NewClient(config *config.Config) (*Client, error) {
@@ -37,48 +38,47 @@
 	return &Client{config: config, client: client}, nil
 }
 
-func (c *Client) VerifyOTP(ctx context.Context, args *commandargs.Shell, otp string) (bool, string, error) {
+func (c *Client) VerifyOTP(ctx context.Context, args *commandargs.Shell, otp string) error {
 	requestBody, err := c.getRequestBody(ctx, args, otp)
 	if err != nil {
-		return false, "", err
+		return err
 	}
 
 	response, err := c.client.Post(ctx, "/two_factor_manual_otp_check", requestBody)
 	if err != nil {
-		return false, "", err
+		return err
 	}
 	defer response.Body.Close()
 
 	return parse(response)
 }
 
-func (c *Client) PushAuth(ctx context.Context, args *commandargs.Shell) (bool, string, error) {
-	// enable push auth in internal rest api
+func (c *Client) PushAuth(ctx context.Context, args *commandargs.Shell) error {
 	requestBody, err := c.getRequestBody(ctx, args, "")
 	if err != nil {
-		return false, "", err
+		return err
 	}
 
 	response, err := c.client.Post(ctx, "/two_factor_push_otp_check", requestBody)
 	if err != nil {
-		return false, "", err
+		return err
 	}
 	defer response.Body.Close()
 
 	return parse(response)
 }
 
-func parse(hr *http.Response) (bool, string, error) {
+func parse(hr *http.Response) error {
 	response := &Response{}
 	if err := gitlabnet.ParseJSON(hr, response); err != nil {
-		return false, "", err
+		return err
 	}
 
 	if !response.Success {
-		return false, response.Message, nil
+		return errors.New(response.Message)
 	}
 
-	return true, response.Message, nil
+	return nil
 }
 
 func (c *Client) getRequestBody(ctx context.Context, args *commandargs.Shell, otp string) (*RequestBody, error) {
diff --git a/internal/gitlabnet/twofactorverify/client_test.go b/internal/gitlabnet/twofactorverify/client_test.go
new file mode 100644
--- /dev/null
+++ b/internal/gitlabnet/twofactorverify/client_test.go
@@ -0,0 +1,208 @@
+package twofactorverify
+
+import (
+	"context"
+	"encoding/json"
+	"io"
+	"net/http"
+	"testing"
+
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
+
+	"github.com/stretchr/testify/require"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+)
+
+func initialize(t *testing.T) []testserver.TestRequestHandler {
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		b, err := io.ReadAll(r.Body)
+		defer r.Body.Close()
+
+		require.NoError(t, err)
+
+		var requestBody *RequestBody
+		require.NoError(t, json.Unmarshal(b, &requestBody))
+
+		switch requestBody.KeyId {
+		case "0":
+			body := map[string]interface{}{
+				"success": true,
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		case "1":
+			body := map[string]interface{}{
+				"success": false,
+				"message": "error message",
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		case "2":
+			w.WriteHeader(http.StatusForbidden)
+			body := &client.ErrorResponse{
+				Message: "Not allowed!",
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		case "3":
+			w.Write([]byte("{ \"message\": \"broken json!\""))
+		case "4":
+			w.WriteHeader(http.StatusForbidden)
+		}
+
+		if requestBody.UserId == 1 {
+			body := map[string]interface{}{
+				"success": true,
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		}
+	}
+
+	requests := []testserver.TestRequestHandler{
+		{
+			Path:    "/api/v4/internal/two_factor_manual_otp_check",
+			Handler: handler,
+		},
+		{
+			Path:    "/api/v4/internal/two_factor_push_otp_check",
+			Handler: handler,
+		},
+		{
+			Path: "/api/v4/internal/discover",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				body := &discover.Response{
+					UserId:   1,
+					Username: "jane-doe",
+					Name:     "Jane Doe",
+				}
+				require.NoError(t, json.NewEncoder(w).Encode(body))
+			},
+		},
+	}
+
+	return requests
+}
+
+const (
+	otpAttempt = "123456"
+)
+
+func TestVerifyOTPByKeyId(t *testing.T) {
+	client := setup(t)
+
+	args := &commandargs.Shell{GitlabKeyId: "0"}
+	err := client.VerifyOTP(context.Background(), args, otpAttempt)
+	require.NoError(t, err)
+}
+
+func TestVerifyOTPByUsername(t *testing.T) {
+	client := setup(t)
+
+	args := &commandargs.Shell{GitlabUsername: "jane-doe"}
+	err := client.VerifyOTP(context.Background(), args, otpAttempt)
+	require.NoError(t, err)
+}
+
+func TestErrorMessage(t *testing.T) {
+	client := setup(t)
+
+	args := &commandargs.Shell{GitlabKeyId: "1"}
+	err := client.VerifyOTP(context.Background(), args, otpAttempt)
+	require.Equal(t, "error message", err.Error())
+}
+
+func TestErrorResponses(t *testing.T) {
+	client := setup(t)
+
+	testCases := []struct {
+		desc          string
+		fakeId        string
+		expectedError string
+	}{
+		{
+			desc:          "A response with an error message",
+			fakeId:        "2",
+			expectedError: "Not allowed!",
+		},
+		{
+			desc:          "A response with bad JSON",
+			fakeId:        "3",
+			expectedError: "Parsing failed",
+		},
+		{
+			desc:          "An error response without message",
+			fakeId:        "4",
+			expectedError: "Internal API error (403)",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			args := &commandargs.Shell{GitlabKeyId: tc.fakeId}
+			err := client.VerifyOTP(context.Background(), args, otpAttempt)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
+
+func TestVerifyPush(t *testing.T) {
+	client := setup(t)
+
+	args := &commandargs.Shell{GitlabKeyId: "0"}
+	err := client.PushAuth(context.Background(), args)
+	require.NoError(t, err)
+}
+
+func TestErrorMessagePush(t *testing.T) {
+	client := setup(t)
+
+	args := &commandargs.Shell{GitlabKeyId: "1"}
+	err := client.PushAuth(context.Background(), args)
+	require.Equal(t, "error message", err.Error())
+}
+
+func TestErrorResponsesPush(t *testing.T) {
+	client := setup(t)
+
+	testCases := []struct {
+		desc          string
+		fakeId        string
+		expectedError string
+	}{
+		{
+			desc:          "A response with an error message",
+			fakeId:        "2",
+			expectedError: "Not allowed!",
+		},
+		{
+			desc:          "A response with bad JSON",
+			fakeId:        "3",
+			expectedError: "Parsing failed",
+		},
+		{
+			desc:          "An error response without message",
+			fakeId:        "4",
+			expectedError: "Internal API error (403)",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			args := &commandargs.Shell{GitlabKeyId: tc.fakeId}
+			err := client.PushAuth(context.Background(), args)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
+
+func setup(t *testing.T) *Client {
+	requests := initialize(t)
+	url := testserver.StartSocketHttpServer(t, requests)
+
+	client, err := NewClient(&config.Config{GitlabUrl: url})
+	require.NoError(t, err)
+
+	return client
+}
diff --git a/internal/gitlabnet/twofactorverify/clientmanual_test.go b/internal/gitlabnet/twofactorverify/clientmanual_test.go
deleted file mode 100644
--- a/internal/gitlabnet/twofactorverify/clientmanual_test.go
+++ /dev/null
@@ -1,151 +0,0 @@
-package twofactorverify
-
-import (
-	"context"
-	"encoding/json"
-	"io"
-	"net/http"
-	"testing"
-
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
-
-	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
-)
-
-func initializeManual(t *testing.T) []testserver.TestRequestHandler {
-	requests := []testserver.TestRequestHandler{
-		{
-			Path: "/api/v4/internal/two_factor_manual_otp_check",
-			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := io.ReadAll(r.Body)
-				defer r.Body.Close()
-
-				require.NoError(t, err)
-
-				var requestBody *RequestBody
-				require.NoError(t, json.Unmarshal(b, &requestBody))
-
-				switch requestBody.KeyId {
-				case "0":
-					body := map[string]interface{}{
-						"success": true,
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "1":
-					body := map[string]interface{}{
-						"success": false,
-						"message": "error message",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "2":
-					w.WriteHeader(http.StatusForbidden)
-					body := &client.ErrorResponse{
-						Message: "Not allowed!",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "3":
-					w.Write([]byte("{ \"message\": \"broken json!\""))
-				case "4":
-					w.WriteHeader(http.StatusForbidden)
-				}
-
-				if requestBody.UserId == 1 {
-					body := map[string]interface{}{
-						"success": true,
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				}
-			},
-		},
-		{
-			Path: "/api/v4/internal/discover",
-			Handler: func(w http.ResponseWriter, r *http.Request) {
-				body := &discover.Response{
-					UserId:   1,
-					Username: "jane-doe",
-					Name:     "Jane Doe",
-				}
-				require.NoError(t, json.NewEncoder(w).Encode(body))
-			},
-		},
-	}
-
-	return requests
-}
-
-const (
-	manualOtpAttempt = "123456"
-)
-
-func TestVerifyOTPByKeyId(t *testing.T) {
-	client := setupManual(t)
-
-	args := &commandargs.Shell{GitlabKeyId: "0"}
-	_, _, err := client.VerifyOTP(context.Background(), args, manualOtpAttempt)
-	require.NoError(t, err)
-}
-
-func TestVerifyOTPByUsername(t *testing.T) {
-	client := setupManual(t)
-
-	args := &commandargs.Shell{GitlabUsername: "jane-doe"}
-	_, _, err := client.VerifyOTP(context.Background(), args, manualOtpAttempt)
-	require.NoError(t, err)
-}
-
-func TestErrorMessage(t *testing.T) {
-	client := setupManual(t)
-
-	args := &commandargs.Shell{GitlabKeyId: "1"}
-	_, reason, _ := client.VerifyOTP(context.Background(), args, manualOtpAttempt)
-	require.Equal(t, "error message", reason)
-}
-
-func TestErrorResponses(t *testing.T) {
-	client := setupManual(t)
-
-	testCases := []struct {
-		desc          string
-		fakeId        string
-		expectedError string
-	}{
-		{
-			desc:          "A response with an error message",
-			fakeId:        "2",
-			expectedError: "Not allowed!",
-		},
-		{
-			desc:          "A response with bad JSON",
-			fakeId:        "3",
-			expectedError: "Parsing failed",
-		},
-		{
-			desc:          "An error response without message",
-			fakeId:        "4",
-			expectedError: "Internal API error (403)",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			args := &commandargs.Shell{GitlabKeyId: tc.fakeId}
-			_, _, err := client.VerifyOTP(context.Background(), args, manualOtpAttempt)
-
-			require.EqualError(t, err, tc.expectedError)
-		})
-	}
-}
-
-func setupManual(t *testing.T) *Client {
-	requests := initializeManual(t)
-	url := testserver.StartSocketHttpServer(t, requests)
-
-	client, err := NewClient(&config.Config{GitlabUrl: url})
-	require.NoError(t, err)
-
-	return client
-}
diff --git a/internal/gitlabnet/twofactorverify/clientpush_test.go b/internal/gitlabnet/twofactorverify/clientpush_test.go
deleted file mode 100644
--- a/internal/gitlabnet/twofactorverify/clientpush_test.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package twofactorverify
-
-import (
-	"context"
-	"encoding/json"
-	"io"
-	"net/http"
-	"testing"
-
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
-
-	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
-)
-
-func initializePush(t *testing.T) []testserver.TestRequestHandler {
-	requests := []testserver.TestRequestHandler{
-		{
-			Path: "/api/v4/internal/two_factor_push_otp_check",
-			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := io.ReadAll(r.Body)
-				defer r.Body.Close()
-
-				require.NoError(t, err)
-
-				var requestBody *RequestBody
-				require.NoError(t, json.Unmarshal(b, &requestBody))
-
-				switch requestBody.KeyId {
-				case "0":
-					body := map[string]interface{}{
-						"success": true,
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "1":
-					body := map[string]interface{}{
-						"success": false,
-						"message": "error message",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "2":
-					w.WriteHeader(http.StatusForbidden)
-					body := &client.ErrorResponse{
-						Message: "Not allowed!",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "3":
-					w.Write([]byte("{ \"message\": \"broken json!\""))
-				case "4":
-					w.WriteHeader(http.StatusForbidden)
-				}
-
-				if requestBody.UserId == 1 {
-					body := map[string]interface{}{
-						"success": true,
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				}
-			},
-		},
-		{
-			Path: "/api/v4/internal/discover",
-			Handler: func(w http.ResponseWriter, r *http.Request) {
-				body := &discover.Response{
-					UserId:   1,
-					Username: "jane-doe",
-					Name:     "Jane Doe",
-				}
-				require.NoError(t, json.NewEncoder(w).Encode(body))
-			},
-		},
-	}
-
-	return requests
-}
-
-func TestVerifyPush(t *testing.T) {
-	client := setupPush(t)
-
-	args := &commandargs.Shell{GitlabKeyId: "0"}
-	_, _, err := client.PushAuth(context.Background(), args)
-	require.NoError(t, err)
-}
-
-func TestErrorMessagePush(t *testing.T) {
-	client := setupPush(t)
-
-	args := &commandargs.Shell{GitlabKeyId: "1"}
-	_, reason, _ := client.PushAuth(context.Background(), args)
-	require.Equal(t, "error message", reason)
-}
-
-func TestErrorResponsesPush(t *testing.T) {
-	client := setupPush(t)
-
-	testCases := []struct {
-		desc          string
-		fakeId        string
-		expectedError string
-	}{
-		{
-			desc:          "A response with an error message",
-			fakeId:        "2",
-			expectedError: "Not allowed!",
-		},
-		{
-			desc:          "A response with bad JSON",
-			fakeId:        "3",
-			expectedError: "Parsing failed",
-		},
-		{
-			desc:          "An error response without message",
-			fakeId:        "4",
-			expectedError: "Internal API error (403)",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			args := &commandargs.Shell{GitlabKeyId: tc.fakeId}
-			_, _, err := client.PushAuth(context.Background(), args)
-
-			require.EqualError(t, err, tc.expectedError)
-		})
-	}
-}
-
-func setupPush(t *testing.T) *Client {
-	requests := initializePush(t)
-	url := testserver.StartSocketHttpServer(t, requests)
-
-	client, err := NewClient(&config.Config{GitlabUrl: url})
-	require.NoError(t, err)
-
-	return client
-}
diff --git a/spec/gitlab_shell_two_factor_manual_verify_spec.rb b/spec/gitlab_shell_two_factor_manual_verify_spec.rb
deleted file mode 100644
--- a/spec/gitlab_shell_two_factor_manual_verify_spec.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-require_relative 'spec_helper'
-
-require 'open3'
-require 'json'
-
-describe 'bin/gitlab-shell 2fa_verify manual' do
-  include_context 'gitlab shell'
-
-  let(:env) do
-    { 'SSH_CONNECTION' => 'fake',
-      'SSH_ORIGINAL_COMMAND' => '2fa_verify' }
-  end
-
-  before(:context) do
-    write_config('gitlab_url' => "http+unix://#{CGI.escape(tmp_socket_path)}")
-  end
-
-  def mock_server(server)
-    server.mount_proc('/api/v4/internal/two_factor_manual_otp_check') do |req, res|
-      res.content_type = 'application/json'
-      res.status = 200
-
-      params = JSON.parse(req.body)
-      key_id = params['key_id'] || params['user_id'].to_s
-
-      if key_id == '100'
-        res.body = { success: true }.to_json
-      else
-        res.body = { success: false, message: 'boom!' }.to_json
-      end
-    end
-
-    server.mount_proc('/api/v4/internal/two_factor_push_otp_check') do |req, res|
-      res.content_type = 'application/json'
-      res.status = 200
-
-      params = JSON.parse(req.body)
-      key_id = params['key_id'] || params['user_id'].to_s
-
-      sleep 5 # simulate Fortinet API timing
-      res.body = { success: false, message: 'boom!' }.to_json
-    end
-
-    server.mount_proc('/api/v4/internal/discover') do |_, res|
-      res.status = 200
-      res.content_type = 'application/json'
-      res.body = { id: 100, name: 'Some User', username: 'someuser' }.to_json
-    end
-  end
-
-  describe 'command' do
-    context 'when key is provided' do
-      let(:cmd) { "#{gitlab_shell_path} key-100" }
-
-      it 'prints a successful verification message' do
-        verify_successful_verification!(cmd)
-      end
-    end
-
-    context 'when username is provided' do
-      let(:cmd) { "#{gitlab_shell_path} username-someone" }
-
-      it 'prints a successful verification message' do
-        verify_successful_verification!(cmd)
-      end
-    end
-
-    context 'when API error occurs' do
-      let(:cmd) { "#{gitlab_shell_path} key-101" }
-
-      it 'prints the error message' do
-        Open3.popen2(env, cmd) do |stdin, stdout|
-          expect(stdout.gets(5)).to eq('OTP: ')
-
-          stdin.puts('123456')
-
-          expect(stdout.flush.read).to eq("\nOTP validation failed.\nboom!\n")
-        end
-      end
-    end
-  end
-
-  def verify_successful_verification!(cmd)
-    Open3.popen2(env, cmd) do |stdin, stdout|
-      expect(stdout.gets(5)).to eq('OTP: ')
-
-      stdin.puts('123456')
-
-      expect(stdout.flush.read).to eq("\nOTP validation successful. Git operations are now allowed.\n")
-    end
-  end
-end
diff --git a/spec/gitlab_shell_two_factor_push_verify_spec.rb b/spec/gitlab_shell_two_factor_push_verify_spec.rb
deleted file mode 100644
--- a/spec/gitlab_shell_two_factor_push_verify_spec.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-require_relative 'spec_helper'
-
-require 'open3'
-require 'json'
-
-describe 'bin/gitlab-shell 2fa_verify push' do
-  include_context 'gitlab shell'
-
-  let(:env) do
-    { 'SSH_CONNECTION' => 'fake',
-      'SSH_ORIGINAL_COMMAND' => '2fa_verify' }
-  end
-
-  before(:context) do
-    write_config('gitlab_url' => "http+unix://#{CGI.escape(tmp_socket_path)}")
-  end
-
-  def mock_server(server)
-    server.mount_proc('/api/v4/internal/two_factor_push_otp_check') do |req, res|
-      res.content_type = 'application/json'
-      res.status = 200
-
-      params = JSON.parse(req.body)
-      key_id = params['key_id'] || params['user_id'].to_s
-
-      if key_id == '100'
-        res.body = { success: false }.to_json
-      elsif key_id == '102'
-        res.body = { success: true }.to_json
-      else
-        res.body = { success: false, message: 'boom!' }.to_json
-      end
-    end
-
-    server.mount_proc('/api/v4/internal/discover') do |_, res|
-      res.status = 200
-      res.content_type = 'application/json'
-      res.body = { id: 100, name: 'Some User', username: 'someuser' }.to_json
-    end
-  end
-
-  describe 'command' do
-    context 'when push is provided' do
-      let(:cmd) { "#{gitlab_shell_path} key-102" }
-
-      it 'prints a successful push verification message' do
-        verify_successful_verification_push!(cmd)
-      end
-    end
-
-    context 'when API error occurs' do
-      let(:cmd) { "#{gitlab_shell_path} key-101" }
-
-      it 'prints the error message' do
-        Open3.popen2(env, cmd) do |stdin, stdout|
-          expect(stdout.gets(5)).to eq('OTP: ')
-
-          expect(stdout.flush.read).to eq("\nPush OTP validation failed.\nboom!\n")
-        end
-      end
-    end
-  end
-
-  def verify_successful_verification_push!(cmd)
-    Open3.popen2(env, cmd) do |stdin, stdout|
-      expect(stdout.gets(5)).to eq('OTP: ')
-
-      expect(stdout.flush.read).to eq("\nPush OTP validation successful. Git operations are now allowed.\n")
-    end
-  end
-end
diff --git a/spec/gitlab_shell_two_factor_verify_spec.rb b/spec/gitlab_shell_two_factor_verify_spec.rb
new file mode 100644
--- /dev/null
+++ b/spec/gitlab_shell_two_factor_verify_spec.rb
@@ -0,0 +1,125 @@
+require_relative 'spec_helper'
+
+require 'open3'
+require 'json'
+
+describe 'bin/gitlab-shell 2fa_verify' do
+  include_context 'gitlab shell'
+
+  let(:env) do
+    { 'SSH_CONNECTION' => 'fake',
+      'SSH_ORIGINAL_COMMAND' => '2fa_verify' }
+  end
+
+  let(:correct_otp) { '123456' }
+
+  before(:context) do
+    write_config('gitlab_url' => "http+unix://#{CGI.escape(tmp_socket_path)}")
+  end
+
+  def mock_server(server)
+    server.mount_proc('/api/v4/internal/two_factor_manual_otp_check') do |req, res|
+      res.content_type = 'application/json'
+      res.status = 200
+
+      params = JSON.parse(req.body)
+
+      res.body = if params['otp_attempt'] == correct_otp
+                   { success: true }.to_json
+                 else
+                   { success: false, message: 'boom!' }.to_json
+                 end
+    end
+
+    server.mount_proc('/api/v4/internal/two_factor_push_otp_check') do |req, res|
+      res.content_type = 'application/json'
+      res.status = 200
+
+      params = JSON.parse(req.body)
+      id = params['key_id'] || params['user_id'].to_s
+
+      if id == '100'
+        res.body = { success: false, message: 'boom!' }.to_json
+      else
+        res.body = { success: true }.to_json
+      end
+    end
+
+    server.mount_proc('/api/v4/internal/discover') do |req, res|
+      res.status = 200
+      res.content_type = 'application/json'
+
+      if req.query['username'] == 'someone'
+        res.body = { id: 100, name: 'Some User', username: 'someuser' }.to_json
+      else
+        res.body = { id: 101, name: 'Another User', username: 'another' }.to_json
+      end
+    end
+  end
+
+  describe 'entering OTP manually' do
+    let(:cmd) { "#{gitlab_shell_path} key-100" }
+
+    context 'when key is provided' do
+      it 'asks a user for a correct OTP' do
+        verify_successful_otp_verification!(cmd)
+      end
+    end
+
+    context 'when username is provided' do
+      let(:cmd) { "#{gitlab_shell_path} username-someone" }
+
+      it 'asks a user for a correct OTP' do
+        verify_successful_otp_verification!(cmd)
+      end
+    end
+
+    it 'shows an error when an invalid otp is provided' do
+      Open3.popen2(env, cmd) do |stdin, stdout|
+        asks_for_otp(stdout)
+        stdin.puts('000000')
+
+        expect(stdout.flush.read).to eq("\nOTP validation failed: boom!\n")
+      end
+    end
+  end
+
+  describe 'authorizing via push' do
+    context 'when key is provided' do
+      let(:cmd) { "#{gitlab_shell_path} key-101" }
+
+      it 'asks a user for a correct OTP' do
+        verify_successful_push_verification!(cmd)
+      end
+    end
+
+    context 'when username is provided' do
+      let(:cmd) { "#{gitlab_shell_path} username-another" }
+
+      it 'asks a user for a correct OTP' do
+        verify_successful_push_verification!(cmd)
+      end
+    end
+  end
+
+  def verify_successful_otp_verification!(cmd)
+    Open3.popen2(env, cmd) do |stdin, stdout|
+      asks_for_otp(stdout)
+      stdin.puts(correct_otp)
+
+      expect(stdout.flush.read).to eq("\nOTP validation successful. Git operations are now allowed.\n")
+    end
+  end
+
+  def verify_successful_push_verification!(cmd)
+    Open3.popen2(env, cmd) do |stdin, stdout|
+      asks_for_otp(stdout)
+
+      expect(stdout.flush.read).to eq("\nOTP has been validated by Push Authentication. Git operations are now allowed.\n")
+    end
+  end
+
+  def asks_for_otp(stdout)
+    expect(stdout.gets(5)).to eq('OTP: ')
+  end
+end
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1658385082 0
#      Thu Jul 21 06:31:22 2022 +0000
# Node ID 4f37a339e4318a318d334a4acd0b534c85769528
# Parent  7517f48a5d24f1415f58523c3e2ee1ec72bd437a
# Parent  3b8480785bad7d68dfac81978e90675bfbc81121
Merge branch '506-twofactorverify-command-to-support-push-notification' into 'main'

Implement Push Auth support for 2FA verification

Closes #506

See merge request gitlab-org/gitlab-shell!454

diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -4,6 +4,7 @@
 	"context"
 	"fmt"
 	"io"
+	"time"
 
 	"gitlab.com/gitlab-org/labkit/log"
 
@@ -13,6 +14,11 @@
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
 )
 
+const (
+	timeout = 30 * time.Second
+	prompt  = "OTP: "
+)
+
 type Command struct {
 	Config     *config.Config
 	Args       *commandargs.Shell
@@ -20,25 +26,51 @@
 }
 
 func (c *Command) Execute(ctx context.Context) error {
-	ctxlog := log.ContextLogger(ctx)
-	ctxlog.Info("twofactorverify: execute: waiting for user input")
-	otp := c.getOTP(ctx)
-
-	ctxlog.Info("twofactorverify: execute: verifying entered OTP")
-	err := c.verifyOTP(ctx, otp)
+	client, err := twofactorverify.NewClient(c.Config)
 	if err != nil {
-		ctxlog.WithError(err).Error("twofactorverify: execute: OTP verification failed")
 		return err
 	}
 
-	ctxlog.WithError(err).Info("twofactorverify: execute: OTP verified")
+	ctx, cancel := context.WithTimeout(ctx, timeout)
+	defer cancel()
+
+	fmt.Fprint(c.ReadWriter.Out, prompt)
+
+	resultCh := make(chan string)
+	go func() {
+		err := client.PushAuth(ctx, c.Args)
+		if err == nil {
+			resultCh <- "OTP has been validated by Push Authentication. Git operations are now allowed."
+		}
+	}()
+
+	go func() {
+		answer, err := c.getOTP(ctx)
+		if err != nil {
+			resultCh <- formatErr(err)
+		}
+
+		if err := client.VerifyOTP(ctx, c.Args, answer); err != nil {
+			resultCh <- formatErr(err)
+		} else {
+			resultCh <- "OTP validation successful. Git operations are now allowed."
+		}
+	}()
+
+	var message string
+	select {
+	case message = <-resultCh:
+	case <-ctx.Done():
+		message = formatErr(ctx.Err())
+	}
+
+	log.WithContextFields(ctx, log.Fields{"message": message}).Info("Two factor verify command finished")
+	fmt.Fprintf(c.ReadWriter.Out, "\n%v\n", message)
+
 	return nil
 }
 
-func (c *Command) getOTP(ctx context.Context) string {
-	prompt := "OTP: "
-	fmt.Fprint(c.ReadWriter.Out, prompt)
-
+func (c *Command) getOTP(ctx context.Context) (string, error) {
 	var answer string
 	otpLength := int64(64)
 	reader := io.LimitReader(c.ReadWriter.In, otpLength)
@@ -46,21 +78,13 @@
 		log.ContextLogger(ctx).WithError(err).Debug("twofactorverify: getOTP: Failed to get user input")
 	}
 
-	return answer
-}
-
-func (c *Command) verifyOTP(ctx context.Context, otp string) error {
-	client, err := twofactorverify.NewClient(c.Config)
-	if err != nil {
-		return err
+	if answer == "" {
+		return "", fmt.Errorf("OTP cannot be blank.")
 	}
 
-	err = client.VerifyOTP(ctx, c.Args, otp)
-	if err == nil {
-		fmt.Fprint(c.ReadWriter.Out, "\nOTP validation successful. Git operations are now allowed.\n")
-	} else {
-		fmt.Fprintf(c.ReadWriter.Out, "\nOTP validation failed.\n%v\n", err)
-	}
+	return answer, nil
+}
 
-	return nil
+func formatErr(err error) string {
+	return fmt.Sprintf("OTP validation failed: %v", err)
 }
diff --git a/internal/command/twofactorverify/twofactorverify_test.go b/internal/command/twofactorverify/twofactorverify_test.go
--- a/internal/command/twofactorverify/twofactorverify_test.go
+++ b/internal/command/twofactorverify/twofactorverify_test.go
@@ -17,10 +17,20 @@
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
 )
 
+type blockingReader struct{}
+
+func (*blockingReader) Read([]byte) (int, error) {
+	waitInfinitely := make(chan struct{})
+	<-waitInfinitely
+
+	return 0, nil
+}
+
 func setup(t *testing.T) []testserver.TestRequestHandler {
+	waitInfinitely := make(chan struct{})
 	requests := []testserver.TestRequestHandler{
 		{
-			Path: "/api/v4/internal/two_factor_otp_check",
+			Path: "/api/v4/internal/two_factor_manual_otp_check",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
@@ -31,11 +41,13 @@
 				require.NoError(t, json.Unmarshal(b, &requestBody))
 
 				switch requestBody.KeyId {
-				case "1":
+				case "verify_via_otp", "verify_via_otp_with_push_error":
 					body := map[string]interface{}{
 						"success": true,
 					}
 					json.NewEncoder(w).Encode(body)
+				case "wait_infinitely":
+					<-waitInfinitely
 				case "error":
 					body := map[string]interface{}{
 						"success": false,
@@ -47,15 +59,36 @@
 				}
 			},
 		},
+		{
+			Path: "/api/v4/internal/two_factor_push_otp_check",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				b, err := io.ReadAll(r.Body)
+				defer r.Body.Close()
+
+				require.NoError(t, err)
+
+				var requestBody *twofactorverify.RequestBody
+				require.NoError(t, json.Unmarshal(b, &requestBody))
+
+				switch requestBody.KeyId {
+				case "verify_via_push":
+					body := map[string]interface{}{
+						"success": true,
+					}
+					json.NewEncoder(w).Encode(body)
+				case "verify_via_otp_with_push_error":
+					w.WriteHeader(http.StatusInternalServerError)
+				default:
+					<-waitInfinitely
+				}
+			},
+		},
 	}
 
 	return requests
 }
 
-const (
-	question    = "OTP: \n"
-	errorHeader = "OTP validation failed.\n"
-)
+const errorHeader = "OTP validation failed: "
 
 func TestExecute(t *testing.T) {
 	requests := setup(t)
@@ -65,46 +98,61 @@
 	testCases := []struct {
 		desc           string
 		arguments      *commandargs.Shell
-		answer         string
+		input          io.Reader
 		expectedOutput string
 	}{
 		{
-			desc:      "With a known key id",
-			arguments: &commandargs.Shell{GitlabKeyId: "1"},
-			answer:    "123456\n",
-			expectedOutput: question +
-				"OTP validation successful. Git operations are now allowed.\n",
+			desc:           "Verify via OTP",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_otp"},
+			expectedOutput: "OTP validation successful. Git operations are now allowed.\n",
+		},
+		{
+			desc:           "Verify via OTP",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_otp_with_push_error"},
+			expectedOutput: "OTP validation successful. Git operations are now allowed.\n",
+		},
+		{
+			desc:           "Verify via push authentication",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_push"},
+			input:          &blockingReader{},
+			expectedOutput: "OTP has been validated by Push Authentication. Git operations are now allowed.\n",
+		},
+		{
+			desc:           "With an empty OTP",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_otp"},
+			input:          bytes.NewBufferString("\n"),
+			expectedOutput: errorHeader + "OTP cannot be blank.\n",
 		},
 		{
 			desc:           "With bad response",
 			arguments:      &commandargs.Shell{GitlabKeyId: "-1"},
-			answer:         "123456\n",
-			expectedOutput: question + errorHeader + "Parsing failed\n",
+			expectedOutput: errorHeader + "Parsing failed\n",
 		},
 		{
 			desc:           "With API returns an error",
 			arguments:      &commandargs.Shell{GitlabKeyId: "error"},
-			answer:         "yes\n",
-			expectedOutput: question + errorHeader + "error message\n",
+			expectedOutput: errorHeader + "error message\n",
 		},
 		{
 			desc:           "With API fails",
 			arguments:      &commandargs.Shell{GitlabKeyId: "broken"},
-			answer:         "yes\n",
-			expectedOutput: question + errorHeader + "Internal API error (500)\n",
+			expectedOutput: errorHeader + "Internal API error (500)\n",
 		},
 		{
 			desc:           "With missing arguments",
 			arguments:      &commandargs.Shell{},
-			answer:         "yes\n",
-			expectedOutput: question + errorHeader + "who='' is invalid\n",
+			expectedOutput: errorHeader + "who='' is invalid\n",
 		},
 	}
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
 			output := &bytes.Buffer{}
-			input := bytes.NewBufferString(tc.answer)
+
+			input := tc.input
+			if input == nil {
+				input = bytes.NewBufferString("123456\n")
+			}
 
 			cmd := &Command{
 				Config:     &config.Config{GitlabUrl: url},
@@ -115,7 +163,29 @@
 			err := cmd.Execute(context.Background())
 
 			require.NoError(t, err)
-			require.Equal(t, tc.expectedOutput, output.String())
+			require.Equal(t, prompt+"\n"+tc.expectedOutput, output.String())
 		})
 	}
 }
+
+func TestCanceledContext(t *testing.T) {
+	requests := setup(t)
+
+	output := &bytes.Buffer{}
+
+	url := testserver.StartSocketHttpServer(t, requests)
+	cmd := &Command{
+		Config:     &config.Config{GitlabUrl: url},
+		Args:       &commandargs.Shell{GitlabKeyId: "wait_infinitely"},
+		ReadWriter: &readwriter.ReadWriter{Out: output, In: &bytes.Buffer{}},
+	}
+
+	ctx, cancel := context.WithCancel(context.Background())
+
+	errCh := make(chan error)
+	go func() { errCh <- cmd.Execute(ctx) }()
+	cancel()
+
+	require.NoError(t, <-errCh)
+	require.Equal(t, prompt+"\n"+errorHeader+"context canceled\n", output.String())
+}
diff --git a/internal/gitlabnet/twofactorverify/client.go b/internal/gitlabnet/twofactorverify/client.go
--- a/internal/gitlabnet/twofactorverify/client.go
+++ b/internal/gitlabnet/twofactorverify/client.go
@@ -26,7 +26,7 @@
 type RequestBody struct {
 	KeyId      string `json:"key_id,omitempty"`
 	UserId     int64  `json:"user_id,omitempty"`
-	OTPAttempt string `json:"otp_attempt"`
+	OTPAttempt string `json:"otp_attempt,omitempty"`
 }
 
 func NewClient(config *config.Config) (*Client, error) {
@@ -44,7 +44,22 @@
 		return err
 	}
 
-	response, err := c.client.Post(ctx, "/two_factor_otp_check", requestBody)
+	response, err := c.client.Post(ctx, "/two_factor_manual_otp_check", requestBody)
+	if err != nil {
+		return err
+	}
+	defer response.Body.Close()
+
+	return parse(response)
+}
+
+func (c *Client) PushAuth(ctx context.Context, args *commandargs.Shell) error {
+	requestBody, err := c.getRequestBody(ctx, args, "")
+	if err != nil {
+		return err
+	}
+
+	response, err := c.client.Post(ctx, "/two_factor_push_otp_check", requestBody)
 	if err != nil {
 		return err
 	}
diff --git a/internal/gitlabnet/twofactorverify/client_test.go b/internal/gitlabnet/twofactorverify/client_test.go
--- a/internal/gitlabnet/twofactorverify/client_test.go
+++ b/internal/gitlabnet/twofactorverify/client_test.go
@@ -17,49 +17,55 @@
 )
 
 func initialize(t *testing.T) []testserver.TestRequestHandler {
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		b, err := io.ReadAll(r.Body)
+		defer r.Body.Close()
+
+		require.NoError(t, err)
+
+		var requestBody *RequestBody
+		require.NoError(t, json.Unmarshal(b, &requestBody))
+
+		switch requestBody.KeyId {
+		case "0":
+			body := map[string]interface{}{
+				"success": true,
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		case "1":
+			body := map[string]interface{}{
+				"success": false,
+				"message": "error message",
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		case "2":
+			w.WriteHeader(http.StatusForbidden)
+			body := &client.ErrorResponse{
+				Message: "Not allowed!",
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		case "3":
+			w.Write([]byte("{ \"message\": \"broken json!\""))
+		case "4":
+			w.WriteHeader(http.StatusForbidden)
+		}
+
+		if requestBody.UserId == 1 {
+			body := map[string]interface{}{
+				"success": true,
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		}
+	}
+
 	requests := []testserver.TestRequestHandler{
 		{
-			Path: "/api/v4/internal/two_factor_otp_check",
-			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := io.ReadAll(r.Body)
-				defer r.Body.Close()
-
-				require.NoError(t, err)
-
-				var requestBody *RequestBody
-				require.NoError(t, json.Unmarshal(b, &requestBody))
-
-				switch requestBody.KeyId {
-				case "0":
-					body := map[string]interface{}{
-						"success": true,
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "1":
-					body := map[string]interface{}{
-						"success": false,
-						"message": "error message",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "2":
-					w.WriteHeader(http.StatusForbidden)
-					body := &client.ErrorResponse{
-						Message: "Not allowed!",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "3":
-					w.Write([]byte("{ \"message\": \"broken json!\""))
-				case "4":
-					w.WriteHeader(http.StatusForbidden)
-				}
-
-				if requestBody.UserId == 1 {
-					body := map[string]interface{}{
-						"success": true,
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				}
-			},
+			Path:    "/api/v4/internal/two_factor_manual_otp_check",
+			Handler: handler,
+		},
+		{
+			Path:    "/api/v4/internal/two_factor_push_otp_check",
+			Handler: handler,
 		},
 		{
 			Path: "/api/v4/internal/discover",
@@ -140,6 +146,57 @@
 	}
 }
 
+func TestVerifyPush(t *testing.T) {
+	client := setup(t)
+
+	args := &commandargs.Shell{GitlabKeyId: "0"}
+	err := client.PushAuth(context.Background(), args)
+	require.NoError(t, err)
+}
+
+func TestErrorMessagePush(t *testing.T) {
+	client := setup(t)
+
+	args := &commandargs.Shell{GitlabKeyId: "1"}
+	err := client.PushAuth(context.Background(), args)
+	require.Equal(t, "error message", err.Error())
+}
+
+func TestErrorResponsesPush(t *testing.T) {
+	client := setup(t)
+
+	testCases := []struct {
+		desc          string
+		fakeId        string
+		expectedError string
+	}{
+		{
+			desc:          "A response with an error message",
+			fakeId:        "2",
+			expectedError: "Not allowed!",
+		},
+		{
+			desc:          "A response with bad JSON",
+			fakeId:        "3",
+			expectedError: "Parsing failed",
+		},
+		{
+			desc:          "An error response without message",
+			fakeId:        "4",
+			expectedError: "Internal API error (403)",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			args := &commandargs.Shell{GitlabKeyId: tc.fakeId}
+			err := client.PushAuth(context.Background(), args)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
+
 func setup(t *testing.T) *Client {
 	requests := initialize(t)
 	url := testserver.StartSocketHttpServer(t, requests)
diff --git a/spec/gitlab_shell_two_factor_verify_spec.rb b/spec/gitlab_shell_two_factor_verify_spec.rb
--- a/spec/gitlab_shell_two_factor_verify_spec.rb
+++ b/spec/gitlab_shell_two_factor_verify_spec.rb
@@ -11,71 +11,115 @@
       'SSH_ORIGINAL_COMMAND' => '2fa_verify' }
   end
 
+  let(:correct_otp) { '123456' }
+
   before(:context) do
     write_config('gitlab_url' => "http+unix://#{CGI.escape(tmp_socket_path)}")
   end
 
   def mock_server(server)
-    server.mount_proc('/api/v4/internal/two_factor_otp_check') do |req, res|
+    server.mount_proc('/api/v4/internal/two_factor_manual_otp_check') do |req, res|
+      res.content_type = 'application/json'
+      res.status = 200
+
+      params = JSON.parse(req.body)
+
+      res.body = if params['otp_attempt'] == correct_otp
+                   { success: true }.to_json
+                 else
+                   { success: false, message: 'boom!' }.to_json
+                 end
+    end
+
+    server.mount_proc('/api/v4/internal/two_factor_push_otp_check') do |req, res|
       res.content_type = 'application/json'
       res.status = 200
 
       params = JSON.parse(req.body)
-      key_id = params['key_id'] || params['user_id'].to_s
+      id = params['key_id'] || params['user_id'].to_s
 
-      if key_id == '100'
+      if id == '100'
+        res.body = { success: false, message: 'boom!' }.to_json
+      else
         res.body = { success: true }.to_json
-      else
-        res.body = { success: false, message: 'boom!' }.to_json
       end
     end
 
-    server.mount_proc('/api/v4/internal/discover') do |_, res|
+    server.mount_proc('/api/v4/internal/discover') do |req, res|
       res.status = 200
       res.content_type = 'application/json'
-      res.body = { id: 100, name: 'Some User', username: 'someuser' }.to_json
+
+      if req.query['username'] == 'someone'
+        res.body = { id: 100, name: 'Some User', username: 'someuser' }.to_json
+      else
+        res.body = { id: 101, name: 'Another User', username: 'another' }.to_json
+      end
     end
   end
 
-  describe 'command' do
+  describe 'entering OTP manually' do
+    let(:cmd) { "#{gitlab_shell_path} key-100" }
+
     context 'when key is provided' do
-      let(:cmd) { "#{gitlab_shell_path} key-100" }
-
-      it 'prints a successful verification message' do
-        verify_successful_verification!(cmd)
+      it 'asks a user for a correct OTP' do
+        verify_successful_otp_verification!(cmd)
       end
     end
 
     context 'when username is provided' do
       let(:cmd) { "#{gitlab_shell_path} username-someone" }
 
-      it 'prints a successful verification message' do
-        verify_successful_verification!(cmd)
+      it 'asks a user for a correct OTP' do
+        verify_successful_otp_verification!(cmd)
       end
     end
 
-    context 'when API error occurs' do
-      let(:cmd) { "#{gitlab_shell_path} key-101" }
+    it 'shows an error when an invalid otp is provided' do
+      Open3.popen2(env, cmd) do |stdin, stdout|
+        asks_for_otp(stdout)
+        stdin.puts('000000')
 
-      it 'prints the error message' do
-        Open3.popen2(env, cmd) do |stdin, stdout|
-          expect(stdout.gets(5)).to eq('OTP: ')
-
-          stdin.puts('123456')
-
-          expect(stdout.flush.read).to eq("\nOTP validation failed.\nboom!\n")
-        end
+        expect(stdout.flush.read).to eq("\nOTP validation failed: boom!\n")
       end
     end
   end
 
-  def verify_successful_verification!(cmd)
+  describe 'authorizing via push' do
+    context 'when key is provided' do
+      let(:cmd) { "#{gitlab_shell_path} key-101" }
+
+      it 'asks a user for a correct OTP' do
+        verify_successful_push_verification!(cmd)
+      end
+    end
+
+    context 'when username is provided' do
+      let(:cmd) { "#{gitlab_shell_path} username-another" }
+
+      it 'asks a user for a correct OTP' do
+        verify_successful_push_verification!(cmd)
+      end
+    end
+  end
+
+  def verify_successful_otp_verification!(cmd)
     Open3.popen2(env, cmd) do |stdin, stdout|
-      expect(stdout.gets(5)).to eq('OTP: ')
-
-      stdin.puts('123456')
+      asks_for_otp(stdout)
+      stdin.puts(correct_otp)
 
       expect(stdout.flush.read).to eq("\nOTP validation successful. Git operations are now allowed.\n")
     end
   end
+
+  def verify_successful_push_verification!(cmd)
+    Open3.popen2(env, cmd) do |stdin, stdout|
+      asks_for_otp(stdout)
+
+      expect(stdout.flush.read).to eq("\nOTP has been validated by Push Authentication. Git operations are now allowed.\n")
+    end
+  end
+
+  def asks_for_otp(stdout)
+    expect(stdout.gets(5)).to eq('OTP: ')
+  end
 end
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1658386193 -7200
#      Thu Jul 21 08:49:53 2022 +0200
# Node ID f7211c6193a856706b586df38536047160ca4c53
# Parent  4f37a339e4318a318d334a4acd0b534c85769528
Release v14.10.0

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.10.0
+
+- Implement Push Auth support for 2FA verification !454
+
 v14.9.0
 
 - Update LabKit library to v1.16.0 !668
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.9.0
+14.10.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1658386603 0
#      Thu Jul 21 06:56:43 2022 +0000
# Node ID 45813b7245308037d79785402e6ab9a6b99fd3a2
# Parent  4f37a339e4318a318d334a4acd0b534c85769528
# Parent  f7211c6193a856706b586df38536047160ca4c53
Merge branch 'id-release-14-10-0' into 'main'

Release v14.10.0

See merge request gitlab-org/gitlab-shell!671

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.10.0
+
+- Implement Push Auth support for 2FA verification !454
+
 v14.9.0
 
 - Update LabKit library to v1.16.0 !668
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.9.0
+14.10.0
# HG changeset patch
# User Terri Chu <tchu@gitlab.com>
# Date 1658865867 0
#      Tue Jul 26 20:04:27 2022 +0000
# Node ID 03e2727fa2380c2653835b6863944646b7d7de9c
# Parent  45813b7245308037d79785402e6ab9a6b99fd3a2
Add simple_roulette to Dangerfile

diff --git a/Dangerfile b/Dangerfile
--- a/Dangerfile
+++ b/Dangerfile
@@ -5,5 +5,5 @@
 Gitlab::Dangerfiles.for_project(self) do |gitlab_dangerfiles|
   gitlab_dangerfiles.import_plugins
 
-  gitlab_dangerfiles.import_dangerfiles(except: %w[changelog commit_messages simple_roulette])
+  gitlab_dangerfiles.import_dangerfiles(except: %w[changelog commit_messages])
 end
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1658979485 0
#      Thu Jul 28 03:38:05 2022 +0000
# Node ID 7a403c86ddc5c76f20d2f07f247092aa2afe4837
# Parent  45813b7245308037d79785402e6ab9a6b99fd3a2
# Parent  03e2727fa2380c2653835b6863944646b7d7de9c
Merge branch 'tchu-add-simple-roulette-to-dangerfile' into 'main'

Add simple_roulette to Dangerfile

See merge request gitlab-org/gitlab-shell!672

diff --git a/Dangerfile b/Dangerfile
--- a/Dangerfile
+++ b/Dangerfile
@@ -5,5 +5,5 @@
 Gitlab::Dangerfiles.for_project(self) do |gitlab_dangerfiles|
   gitlab_dangerfiles.import_plugins
 
-  gitlab_dangerfiles.import_dangerfiles(except: %w[changelog commit_messages simple_roulette])
+  gitlab_dangerfiles.import_dangerfiles(except: %w[changelog commit_messages])
 end
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1659706663 -7200
#      Fri Aug 05 15:37:43 2022 +0200
# Node ID 45c060eda1d3ae5ce3447bd7a325c65dda6ef66c
# Parent  7a403c86ddc5c76f20d2f07f247092aa2afe4837
Fix failing TestGitReceivePackSuccess

After https://gitlab.com/gitlab-org/gitaly/-/merge_requests/4766
has been introduced, the test started fail because we basically
cancel the git-receive-pack after the output is received

This commit gracefully closes the connection to make the test
pass

diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -387,12 +387,25 @@
 	ensureGitalyRepository(t)
 
 	client := runSSHD(t, successAPI(t))
-
 	session, err := client.NewSession()
 	require.NoError(t, err)
 	defer session.Close()
 
-	output, err := session.Output(fmt.Sprintf("git-receive-pack %s", testRepo))
+	stdin, err := session.StdinPipe()
+	require.NoError(t, err)
+
+	stdout, err := session.StdoutPipe()
+	require.NoError(t, err)
+
+	err = session.Start(fmt.Sprintf("git-receive-pack %s", testRepo))
+	require.NoError(t, err)
+
+	// Gracefully close connection
+	_, err = fmt.Fprintln(stdin, "0000")
+	require.NoError(t, err)
+	stdin.Close()
+
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 
 	outputLines := strings.Split(string(output), "\n")
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1659714270 0
#      Fri Aug 05 15:44:30 2022 +0000
# Node ID 499500688649ae58968c3d8ea902845f38b1f360
# Parent  7a403c86ddc5c76f20d2f07f247092aa2afe4837
# Parent  45c060eda1d3ae5ce3447bd7a325c65dda6ef66c
Merge branch 'id-fix-git-receive-pack-in-tests' into 'main'

Fix failing TestGitReceivePackSuccess

See merge request gitlab-org/gitlab-shell!675

diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -387,12 +387,25 @@
 	ensureGitalyRepository(t)
 
 	client := runSSHD(t, successAPI(t))
-
 	session, err := client.NewSession()
 	require.NoError(t, err)
 	defer session.Close()
 
-	output, err := session.Output(fmt.Sprintf("git-receive-pack %s", testRepo))
+	stdin, err := session.StdinPipe()
+	require.NoError(t, err)
+
+	stdout, err := session.StdoutPipe()
+	require.NoError(t, err)
+
+	err = session.Start(fmt.Sprintf("git-receive-pack %s", testRepo))
+	require.NoError(t, err)
+
+	// Gracefully close connection
+	_, err = fmt.Fprintln(stdin, "0000")
+	require.NoError(t, err)
+	stdin.Close()
+
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 
 	outputLines := strings.Split(string(output), "\n")
# HG changeset patch
# User Carlos Yu <carlosy@indeed.com>
# Date 1659714942 0
#      Fri Aug 05 15:55:42 2022 +0000
# Node ID a301814a2bf83c73905f9ff6af1623b636d84d87
# Parent  7a403c86ddc5c76f20d2f07f247092aa2afe4837
Fixed extra slashes in API request paths generated for geo

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -77,6 +77,7 @@
 			testAuthenticationHeader(t, client)
 			testJWTAuthenticationHeader(t, client)
 			testXForwardedForHeader(t, client)
+			testHostWithTrailingSlash(t, client)
 		})
 	}
 }
@@ -237,6 +238,16 @@
 	})
 }
 
+func testHostWithTrailingSlash(t *testing.T, client *GitlabNetClient) {
+	oldHost := client.httpClient.Host
+	client.httpClient.Host = oldHost + "/"
+
+	testSuccessfulGet(t, client)
+	testSuccessfulPost(t, client)
+
+	client.httpClient.Host = oldHost
+}
+
 func buildRequests(t *testing.T, relativeURLRoot string) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -85,6 +85,10 @@
 	return path
 }
 
+func appendPath(host string, path string) string {
+	return strings.TrimSuffix(host, "/") + "/" + strings.TrimPrefix(path, "/")
+}
+
 func newRequest(ctx context.Context, method, host, path string, data interface{}) (*http.Request, error) {
 	var jsonReader io.Reader
 	if data != nil {
@@ -96,7 +100,7 @@
 		jsonReader = bytes.NewReader(jsonData)
 	}
 
-	request, err := http.NewRequestWithContext(ctx, method, host+path, jsonReader)
+	request, err := http.NewRequestWithContext(ctx, method, appendPath(host, path), jsonReader)
 	if err != nil {
 		return nil, err
 	}
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1659714943 0
#      Fri Aug 05 15:55:43 2022 +0000
# Node ID 2d47cb0b0251d461399adca678da9819011b2c04
# Parent  499500688649ae58968c3d8ea902845f38b1f360
# Parent  a301814a2bf83c73905f9ff6af1623b636d84d87
Merge branch 'I-365101' into 'main'

Fixed extra slashes in API request paths generated for geo

See merge request gitlab-org/gitlab-shell!673

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -77,6 +77,7 @@
 			testAuthenticationHeader(t, client)
 			testJWTAuthenticationHeader(t, client)
 			testXForwardedForHeader(t, client)
+			testHostWithTrailingSlash(t, client)
 		})
 	}
 }
@@ -237,6 +238,16 @@
 	})
 }
 
+func testHostWithTrailingSlash(t *testing.T, client *GitlabNetClient) {
+	oldHost := client.httpClient.Host
+	client.httpClient.Host = oldHost + "/"
+
+	testSuccessfulGet(t, client)
+	testSuccessfulPost(t, client)
+
+	client.httpClient.Host = oldHost
+}
+
 func buildRequests(t *testing.T, relativeURLRoot string) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -85,6 +85,10 @@
 	return path
 }
 
+func appendPath(host string, path string) string {
+	return strings.TrimSuffix(host, "/") + "/" + strings.TrimPrefix(path, "/")
+}
+
 func newRequest(ctx context.Context, method, host, path string, data interface{}) (*http.Request, error) {
 	var jsonReader io.Reader
 	if data != nil {
@@ -96,7 +100,7 @@
 		jsonReader = bytes.NewReader(jsonData)
 	}
 
-	request, err := http.NewRequestWithContext(ctx, method, host+path, jsonReader)
+	request, err := http.NewRequestWithContext(ctx, method, appendPath(host, path), jsonReader)
 	if err != nil {
 		return nil, err
 	}
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1659707501 -7200
#      Fri Aug 05 15:51:41 2022 +0200
# Node ID 8c20e6acc5d7a3192b2b124dfbfcf80728fd9a36
# Parent  499500688649ae58968c3d8ea902845f38b1f360
Update Gitaly to v15

This commit also excludes gitlab-shell from dependencies:

Gitaly specifies Gitlab Shell as a dependency as well in order
to use gitlabnet client to perform API endpoints to Gitlab Rails.
As a result, Gitlab Shell requires Gitaly -> Gitaly requires an
older version of Gitlab Shell -> that version requires an older
version of Gitlab Shell, etc. Let's use exclude to break the
chain earlier

diff --git a/client/testserver/gitalyserver.go b/client/testserver/gitalyserver.go
--- a/client/testserver/gitalyserver.go
+++ b/client/testserver/gitalyserver.go
@@ -10,8 +10,8 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/labkit/log"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials/insecure"
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -22,8 +22,8 @@
 	"github.com/mikesmitty/edkey"
 	"github.com/pires/go-proxyproto"
 	"github.com/stretchr/testify/require"
-	gitalyClient "gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	gitalyClient "gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 	"golang.org/x/crypto/ssh"
 )
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -10,27 +10,29 @@
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.2
-	github.com/prometheus/client_golang v1.12.1
+	github.com/prometheus/client_golang v1.12.2
 	github.com/sirupsen/logrus v1.8.1
-	github.com/stretchr/testify v1.7.0
-	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
+	github.com/stretchr/testify v1.8.0
+	gitlab.com/gitlab-org/gitaly/v15 v15.2.0
 	gitlab.com/gitlab-org/labkit v1.16.0
-	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
-	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	google.golang.org/grpc v1.40.0
+	golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
+	golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
+	google.golang.org/grpc v1.47.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
 require (
-	cloud.google.com/go v0.92.2 // indirect
+	cloud.google.com/go v0.100.2 // indirect
+	cloud.google.com/go/compute v1.5.0 // indirect
+	cloud.google.com/go/monitoring v1.4.0 // indirect
 	cloud.google.com/go/profiler v0.1.0 // indirect
-	cloud.google.com/go/trace v0.1.0 // indirect
-	contrib.go.opencensus.io/exporter/stackdriver v0.13.8 // indirect
+	cloud.google.com/go/trace v1.2.0 // indirect
+	contrib.go.opencensus.io/exporter/stackdriver v0.13.10 // indirect
 	github.com/DataDog/datadog-go v4.4.0+incompatible // indirect
 	github.com/DataDog/sketches-go v1.0.0 // indirect
 	github.com/Microsoft/go-winio v0.5.0 // indirect
 	github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
-	github.com/aws/aws-sdk-go v1.38.35 // indirect
+	github.com/aws/aws-sdk-go v1.43.31 // indirect
 	github.com/beevik/ntp v0.3.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
@@ -41,12 +43,13 @@
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
-	github.com/google/go-cmp v0.5.6 // indirect
+	github.com/google/go-cmp v0.5.8 // indirect
 	github.com/google/pprof v0.0.0-20210804190019-f964ff605595 // indirect
-	github.com/google/uuid v1.2.0 // indirect
-	github.com/googleapis/gax-go/v2 v2.0.5 // indirect
-	github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 // indirect
+	github.com/google/uuid v1.3.0 // indirect
+	github.com/googleapis/gax-go/v2 v2.2.0 // indirect
+	github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	github.com/kr/text v0.2.0 // indirect
 	github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 // indirect
 	github.com/lightstep/lightstep-tracer-go v0.25.0 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
@@ -63,22 +66,28 @@
 	github.com/tinylib/msgp v1.1.2 // indirect
 	github.com/tklauser/go-sysconf v0.3.4 // indirect
 	github.com/tklauser/numcpus v0.2.1 // indirect
-	github.com/uber/jaeger-client-go v2.29.1+incompatible // indirect
+	github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
 	github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
 	go.opencensus.io v0.23.0 // indirect
-	go.uber.org/atomic v1.7.0 // indirect
-	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
-	golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a // indirect
-	golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
+	go.uber.org/atomic v1.9.0 // indirect
+	golang.org/x/net v0.0.0-20220531201128-c960675eff93 // indirect
+	golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
+	golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
 	golang.org/x/text v0.3.7 // indirect
-	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
+	golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
-	google.golang.org/api v0.54.0 // indirect
+	google.golang.org/api v0.74.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20210813162853-db860fec028c // indirect
-	google.golang.org/protobuf v1.27.1 // indirect
+	google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de // indirect
+	google.golang.org/protobuf v1.28.0 // indirect
 	gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
+	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
+// Gitaly specifies Gitlab Shell as a dependency and causes older versions
+// of Gitaly and Gitlab Shell to be included in go.sum.
+// Let's exclude the older version of Gitlab Shell to break the chain in the beginning
+exclude gitlab.com/gitlab-org/gitlab-shell/v14 v14.8.0
+
 replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -1,9 +1,6 @@
-bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
-bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
 cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
@@ -22,104 +19,118 @@
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
 cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.82.0/go.mod h1:vlKccHJGuFBFufnAnuB08dfEH9Y3H7dzDzRECFdC2TA=
 cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
 cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
 cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
 cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
-cloud.google.com/go v0.92.2 h1:podK44+0gcW5rWGMjJiPH0+rzkCTQx/zT0qF5CLqVkM=
 cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
+cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
+cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
+cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U=
+cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y=
+cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
+cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw=
+cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
+cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM=
+cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo=
+cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
+cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=
+cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw=
+cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc=
+cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
+cloud.google.com/go/kms v1.1.0/go.mod h1:WdbppnCDMDpOvoYBMn1+gNmOeEoZYqAv+HeuKARGCXI=
+cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA=
+cloud.google.com/go/monitoring v1.1.0/go.mod h1:L81pzz7HKn14QCMaCs6NTQkdBnE87TElyanS95vIcl4=
+cloud.google.com/go/monitoring v1.4.0 h1:05+IuNMbh40hbxcqQ4SnynbwZbLG1Wc9dysIJxnfv7U=
+cloud.google.com/go/monitoring v1.4.0/go.mod h1:y6xnxfwI3hTFWOdkOaD7nfJVlwuC3/mS/5kvtT131p4=
 cloud.google.com/go/profiler v0.1.0 h1:MG/rxKC1MztRfEWMGYKFISxyZak5hNh29f0A/z2tvWk=
 cloud.google.com/go/profiler v0.1.0/go.mod h1:D7S7LV/zKbRWkOzYL1b5xytpqt8Ikd/v/yvf1/Tx2pQ=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
 cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
-cloud.google.com/go/pubsub v1.10.3/go.mod h1:FUcc28GpGxxACoklPsE1sCtbkY4Ix+ro7yvw+h82Jn4=
+cloud.google.com/go/pubsub v1.19.0/go.mod h1:/O9kmSe9bb9KRnIAWkzmqhPjHo6LtzGOBYd/kr06XSs=
+cloud.google.com/go/secretmanager v1.3.0/go.mod h1:+oLTkouyiYiabAQNugCeTS3PAArGiMJuBqvJnJsyH+U=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-cloud.google.com/go/storage v1.15.0 h1:Ljj+ZXVEhCr/1+4ZhvtteN1ND7UUsNTlduGclLh8GO0=
-cloud.google.com/go/storage v1.15.0/go.mod h1:mjjQMoxxyGH7Jr8K5qrx6N2O0AHsczI61sMNn03GIZI=
-cloud.google.com/go/trace v0.1.0 h1:nUGUK79FOkN0UGUXhBmVBkbu1PYsHe0YyFSPLOD9Npg=
+cloud.google.com/go/storage v1.21.0 h1:HwnT2u2D309SFDHQII6m18HlrCi3jAXhUMTLOWXYH14=
+cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKuqnZI01LAA=
 cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g=
+cloud.google.com/go/trace v1.0.0/go.mod h1:4iErSByzxkyHWzzlAj63/Gmjz0NH1ASqhJguHpGcr6A=
+cloud.google.com/go/trace v1.2.0 h1:oIaB4KahkIUOpLSAAjEJ8y2desbjY/x/RfP4O3KAtTI=
+cloud.google.com/go/trace v1.2.0/go.mod h1:Wc8y/uYyOhPy12KEnXG9XGrvfMz5F5SrYecQlbW1rwM=
 contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
-contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
-contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.10 h1:a9+GZPUe+ONKUwULjlEOucMMG0qfSCCenlji0Nhqbys=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.10/go.mod h1:I5htMbyta491eUxufwwZPQdcKvvgzMB4O9ni41YnIM8=
 contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
-github.com/Azure/azure-amqp-common-go/v3 v3.1.0/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0=
+github.com/Azure/azure-amqp-common-go/v3 v3.2.1/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI=
+github.com/Azure/azure-amqp-common-go/v3 v3.2.2/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI=
 github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
 github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
-github.com/Azure/azure-sdk-for-go v54.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
-github.com/Azure/azure-service-bus-go v0.10.11/go.mod h1:AWw9eTTWZVZyvgpPahD1ybz3a8/vT3GsJDS8KYex55U=
-github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nUdBD+e64lEuc4sVnuOfNs=
-github.com/Azure/go-amqp v0.13.0/go.mod h1:qj+o8xPCz9tMSbQ83Vp8boHahuRDl5mkNHyt1xlxUTs=
-github.com/Azure/go-amqp v0.13.4/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
-github.com/Azure/go-amqp v0.13.7/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
+github.com/Azure/azure-sdk-for-go v59.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
+github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
+github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU=
+github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck=
+github.com/Azure/go-amqp v0.16.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
+github.com/Azure/go-amqp v0.16.4/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
 github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
-github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
 github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
-github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
-github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE=
+github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
+github.com/Azure/go-autorest/autorest v0.11.22/go.mod h1:BAWYUWGPEtKPzjVkp0Q6an0MJcJDsoh5Z1BFAEFs4Xs=
 github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
-github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
 github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
-github.com/Azure/go-autorest/autorest/azure/auth v0.5.7/go.mod h1:AkzUsqkrdmNhfP2i54HqINVQopw0CLDnvHpJ88Zz1eI=
+github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
+github.com/Azure/go-autorest/autorest/adal v0.9.17/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.9/go.mod h1:hg3/1yw0Bq87O3KvvnJoAh34/0zbP7SFizX/qN5JvjU=
 github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
 github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
-github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
 github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
 github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
 github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
-github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
 github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
-github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w=
 github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
 github.com/DataDog/datadog-go v4.4.0+incompatible h1:R7WqXWP4fIOAqWJtUKmSfuc7eDsBT58k9AY5WSHVosk=
 github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
 github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
 github.com/DataDog/sketches-go v1.0.0 h1:chm5KSXO7kO+ywGWJ0Zs6tdmWU8PBXSbywFVciL6BG4=
 github.com/DataDog/sketches-go v1.0.0/go.mod h1:O+XkJHWk9w4hDwY2ZUDU31ZC9sNYlYo8DiFsxjYeo1k=
-github.com/GoogleCloudPlatform/cloudsql-proxy v1.22.0/go.mod h1:mAm5O/zik2RFmcpigNjg6nMotDL8ZXJaxKzgGVcSMFA=
-github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
+github.com/GoogleCloudPlatform/cloudsql-proxy v1.29.0/go.mod h1:spvB9eLJH9dutlbPSRmHvSXXHOwGRyeXh1jVdquA2G8=
 github.com/HdrHistogram/hdrhistogram-go v1.1.1 h1:cJXY5VLMHgejurPjZH6Fo9rIwRGLefBGdiaENZALqrg=
 github.com/HdrHistogram/hdrhistogram-go v1.1.1/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
-github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
-github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
-github.com/Microsoft/go-winio v0.4.19/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
 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/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
-github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
-github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 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=
-github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
-github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
 github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
@@ -128,40 +139,52 @@
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
-github.com/alexbrainman/sspi v0.0.0-20180125232955-4729b3d4d858/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
+github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
-github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
-github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
-github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
-github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
 github.com/avast/retry-go v2.4.2+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
-github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
+github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
-github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
-github.com/aws/aws-sdk-go v1.38.35 h1:7AlAO0FC+8nFjxiGKEmq0QLpiA8/XFr6eIxgRTwkdTg=
-github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
-github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
+github.com/aws/aws-sdk-go v1.43.31 h1:yJZIr8nMV1hXjAvvOLUFqZRJcHV7udPQBfhJqawDzI0=
+github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
+github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM=
+github.com/aws/aws-sdk-go-v2/config v1.15.3/go.mod h1:9YL3v07Xc/ohTsxFXzan9ZpFpdTOFl4X65BAKYaz8jg=
+github.com/aws/aws-sdk-go-v2/credentials v1.11.2/go.mod h1:j8YsY9TXTm31k4eFhspiQicfXPLZ0gYXA50i4gxPE8g=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3/go.mod h1:uk1vhHHERfSVCUnqSqz8O48LBYDSC+k6brng09jcMOk=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3/go.mod h1:0dHuD2HZZSiwfJSy1FO5bX1hQ1TxVV1QXXjpn3XUE44=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9/go.mod h1:AnVH5pvai0pAF4lXRq0bmhbes1u9R8wTE+g+183bZNM=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10/go.mod h1:8DcYQcz0+ZJaSxANlHIsbbi6S+zMwjwdDqwW3r9AzaE=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3/go.mod h1:Seb8KNmD6kVTjwRjVEgOT5hPin6sq+v4C2ycJQDwuH8=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3/go.mod h1:wlY6SVjuwvh3TVRpTqdy4I1JpBFLX4UGeKZdWntaocw=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3/go.mod h1:Bm/v2IaN6rZ+Op7zX+bOUMdL4fsrYZiD0dsjLhNKwZc=
+github.com/aws/aws-sdk-go-v2/service/kms v1.16.3/go.mod h1:QuiHPBqlOFCi4LqdSskYYAWpQlx3PKmohy+rE2F+o5g=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z4KJ7DtFFCx8fJD2a491Ak=
+github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4/go.mod h1:PJc8s+lxyU8rrre0/4a0pn2wgwiDvOEzoOjcJUBr67o=
+github.com/aws/aws-sdk-go-v2/service/sns v1.17.4/go.mod h1:kElt+uCcXxcqFyc+bQqZPFD9DME/eC6oHBXvFzQ9Bcw=
+github.com/aws/aws-sdk-go-v2/service/sqs v1.18.3/go.mod h1:skmQo0UPvsjsuYYSYMVmrPc1HWCbHUJyrCEp+ZaLzqM=
+github.com/aws/aws-sdk-go-v2/service/ssm v1.24.1/go.mod h1:NR/xoKjdbRJ+qx0pMR4mI+N/H1I1ynHwXnO6FowXJc0=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.3/go.mod h1:7UQ/e69kU7LDPtY40OyoHYgRmgfGM4mgsLYtcObdveU=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.3/go.mod h1:bfBj0iVmsUyUg4weDB4NxktD9rDGeKSVWnjTnwbx9b8=
+github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
 github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
 github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 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/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
-github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 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=
-github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
 github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@@ -171,63 +194,54 @@
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
-github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
+github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
 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/tableflip v0.0.0-20190329062924-8392f1641731/go.mod h1:erh4dYezoMVbIa52pi7i1Du7+cXOgqNuTamt10qvMoA=
-github.com/cloudflare/tableflip v1.2.2 h1:WkhiowHlg0nZuH7Y2beLVIZDfxtSvKta1f22PEgUN7w=
-github.com/cloudflare/tableflip v1.2.2/go.mod h1:P4gRehmV6Z2bY5ao5ml9Pd8u6kuEnlB37pUFMmv7j2E=
+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=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
 github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
-github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
-github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
-github.com/containerd/cgroups v0.0.0-20201118023556-2819c83ced99/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
+github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
-github.com/coreos/go-systemd/v22 v22.3.1/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
-github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
-github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU=
 github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
 github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
 github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
 github.com/dgryski/go-minhash v0.0.0-20170608043002-7fe510aff544/go.mod h1:VBi0XHpFy0xiMySf6YpVbRqrupW4RprJ5QTyN+XvGSM=
 github.com/dgryski/go-spooky v0.0.0-20170606183049-ed3d087f40e2/go.mod h1:hgHYKsoIw7S/hlWtP7wD1wZ7SX1jPTtKko5X9jrOgPQ=
 github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
 github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
+github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/dpotapov/go-spnego v0.0.0-20190506202455-c2c609116ad0/go.mod h1:P4f4MSk7h52F2PK0lCapn5+fu47Uf8aRdxDSqgezxZE=
-github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dpotapov/go-spnego v0.0.0-20210315154721-298b63a54430/go.mod h1:AVSs/gZKt1bOd2AhkhbS7Qh56Hv7klde22yXVbwYJhc=
+github.com/dpotapov/go-spnego v0.0.0-20220426193508-b7f82e4507db/go.mod h1:AVSs/gZKt1bOd2AhkhbS7Qh56Hv7klde22yXVbwYJhc=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
-github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
-github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
-github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
 github.com/ekzhu/minhash-lsh v0.0.0-20171225071031-5c06ee8586a1/go.mod h1:yEtCVi+QamvzjEH4U/m6ZGkALIkF2xfQnFp0BcKmIOk=
 github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
-github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -235,41 +249,37 @@
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
-github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
-github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
-github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
-github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
+github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
-github.com/getsentry/raven-go v0.1.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
-github.com/getsentry/raven-go v0.1.2/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
-github.com/getsentry/sentry-go v0.5.1/go.mod h1:B8H7x8TYDPkeWPRzGpIiFO97LZP6rL8A3hEt8lUItMw=
-github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
-github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
 github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
-github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
 github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
+github.com/gin-gonic/gin v1.7.3/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
 github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
-github.com/git-lfs/git-lfs v1.5.1-0.20210304194248-2e1d981afbe3/go.mod h1:8Xqs4mqL7o6xEnaXckIgELARTeK7RYtm3pBab7S79Js=
-github.com/git-lfs/gitobj/v2 v2.0.1/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
-github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
-github.com/git-lfs/wildmatch v1.0.4/go.mod h1:SdHAGnApDpnFYQ0vAxbniWR0sn7yLJ3QXo9RRfhn2ew=
+github.com/git-lfs/git-lfs/v3 v3.2.0/go.mod h1:GZZO3jw2Yn3/1KFV4nRoXUzH+yPzGypIdTeQpkzxEvQ=
+github.com/git-lfs/gitobj/v2 v2.1.0/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
+github.com/git-lfs/go-netrc v0.0.0-20210914205454-f0c862dd687a/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
+github.com/git-lfs/pktline v0.0.0-20210330133718-06e9096e2825/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
+github.com/git-lfs/wildmatch/v2 v2.0.1/go.mod h1:EVqonpk9mXbREP3N8UkwoWdrF249uHpCUo5CPXY81gw=
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
+github.com/go-enry/go-enry/v2 v2.8.2/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ=
 github.com/go-enry/go-license-detector/v4 v4.3.0/go.mod h1:HaM4wdNxSlz/9Gw0uVOKSQS5JVFqf2Pk8xUPEn6bldI=
+github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
 github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
@@ -284,7 +294,6 @@
 github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@@ -297,7 +306,6 @@
 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
-github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -309,30 +317,25 @@
 github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
 github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
-github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
-github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
 github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
 github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
 github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@@ -361,7 +364,6 @@
 github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -377,11 +379,13 @@
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
-github.com/google/go-replayers/grpcreplay v1.0.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
-github.com/google/go-replayers/httpreplay v0.1.2/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
+github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk=
+github.com/google/go-replayers/httpreplay v1.1.1/go.mod h1:gN9GeLIs7l6NUoVaSSnv2RiqK1NiwAmD0MrKeC9IIks=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -390,6 +394,7 @@
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
+github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -403,6 +408,7 @@
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210125172800-10e9aeb4a998/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
@@ -411,69 +417,50 @@
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
-github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
 github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
+github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
+github.com/googleapis/gax-go/v2 v2.2.0 h1:s7jOdKSaksJVOxE0Y/S32otcfiP+UQ0cL8/GTKaONwE=
+github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
-github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-middleware v1.2.3-0.20210213123510-be4c235f9d1c/go.mod h1:RXwzibsL7UhPcEmGyGvXKJ8kyJsOCOEaLgGce4igMFs=
 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
-github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
+github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
-github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
 github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
-github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
-github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
-github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
-github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
-github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
-github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
-github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
-github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 h1:Y4V+SFe7d3iH+9pJCoeWIOS5/xBJIFsltS7E+KJSsJY=
-github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
+github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I=
+github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
 github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b/go.mod h1:HmaZGXHdSwQh1jnUlBGN2BeEYOHACLVGzYOXCbsLvxY=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
 github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
-github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
 github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
 github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
 github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
@@ -486,7 +473,8 @@
 github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
 github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
 github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
-github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono=
 github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
 github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
 github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
@@ -500,39 +488,42 @@
 github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
 github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
 github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
 github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
 github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
-github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
 github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
 github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
 github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
-github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
+github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
+github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ=
 github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
-github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
+github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
 github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
+github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
+github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 github.com/jdkato/prose v1.1.0/go.mod h1:jkF0lkxaX5PFSlk9l4Gh9Y+T57TqUZziWT7uZbW5ADg=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
 github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
-github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -541,34 +532,23 @@
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jteeuwen/go-bindata v3.0.8-0.20180305030458-6025e8de665b+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
-github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
-github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
-github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
-github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk=
 github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
-github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U=
 github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
-github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw=
 github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
-github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
 github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
 github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
-github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
+github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
 github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
-github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -582,29 +562,21 @@
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
+github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
 github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/leonelquinteros/gotext v1.5.0/go.mod h1:OCiUVHuhP9LGFBQ1oAmdtNCHJCiHiQA8lf4nAifHkr0=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
-github.com/libgit2/git2go/v31 v31.4.12/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec=
+github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go/v33 v33.0.9/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
-github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
-github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7/go.mod h1:Spd59icnvRxSKuyijbbwe5AemzvcyXAUBgApa7VybMw=
-github.com/lightstep/lightstep-tracer-go v0.15.6/go.mod h1:6AMpwZpsyCFwSovxzM78e+AsYxE8sGwiM6C3TytaWeI=
-github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/lightstep/lightstep-tracer-go v0.24.0/go.mod h1:RnONwHKg89zYPmF+Uig5PpHMUcYCFgml8+r4SS53y7A=
 github.com/lightstep/lightstep-tracer-go v0.25.0 h1:sGVnz8h3jTQuHKMbUe2949nXm3Sg09N1UcR3VoQNN5E=
 github.com/lightstep/lightstep-tracer-go v0.25.0/go.mod h1:G1ZAEaqTHFPWpWunnbUn1ADEY/Jvzz7jIOaXwAfD6A8=
-github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
@@ -613,6 +585,7 @@
 github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
+github.com/mattn/go-ieproxy v0.0.3/go.mod h1:6ZpRmhBaYuBX1U2za+9rC9iCGLsSp2tftelZne7CPko=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@@ -621,59 +594,44 @@
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
-github.com/mattn/go-shellwords v0.0.0-20190425161501-2444a32a19f4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-shellwords v1.0.11 h1:vCoR9VPpsk/TZFW2JwK5I9S0xdrtUq2bph6/YjEPnaw=
 github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
 github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg=
-github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
 github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
 github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
-github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
 github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
-github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
-github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
-github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
-github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
 github.com/montanaflynn/stats v0.0.0-20151014174947-eeaced052adb/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
-github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
-github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
-github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
 github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
-github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
 github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
-github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
-github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc=
 github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
-github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -682,86 +640,58 @@
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
-github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
-github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
 github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
-github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
-github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
-github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
-github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
-github.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc=
 github.com/otiai10/copy v1.4.2 h1:RTiz2sol3eoXPLF4o+YWqEybwfUa/Q2Nkc4ZIUs3fwI=
 github.com/otiai10/copy v1.4.2/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E=
 github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
 github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
-github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw=
 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
 github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E=
 github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
-github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
-github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
-github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
-github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
-github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
 github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
-github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
-github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
-github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
 github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
 github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
+github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
+github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -774,19 +704,13 @@
 github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086/go.mod h1:YpdgDXpumPB/+EGmGTYHeiW/0QVFRzBYTNFaxWfPDk4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
-github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
-github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/sebest/xff v0.0.0-20160910043805-6c115e0ffa35/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
-github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY=
-github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
 github.com/shirou/gopsutil/v3 v3.21.2 h1:fIOk3hyqV1oGKogfGNjUZa0lUbtlkx3+ZT0IoJth2uM=
 github.com/shirou/gopsutil/v3 v3.21.2/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
 github.com/shogo82148/go-shuffle v0.0.0-20170808115208-59829097ff3b/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
@@ -795,7 +719,6 @@
 github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
@@ -804,58 +727,45 @@
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/ssgelm/cookiejarparser v1.0.1/go.mod h1:DUfC0mpjIzlDN7DzKjXpHj0qMI5m9VrZuz3wSlI+OEI=
-github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
-github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
-github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
-github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
 github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=
 github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
 github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc=
 github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
-github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-client-go v2.27.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-client-go v2.29.1+incompatible h1:R9ec3zO3sGpzs0abd43Y+fBZRJ9uiH6lXyR/+u6brW4=
 github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
+github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
+github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
 github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
 github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
-github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
-github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
-github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
@@ -868,12 +778,10 @@
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v0.0.0-20170210233622-6b67b3fab74d/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
 github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
 github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
-github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -881,28 +789,14 @@
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
-gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
-gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
-gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
-gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059 h1:X7+3GQIxUpScXpIMCU5+sfpYvZyBIQ3GMlEosP7Jssw=
-gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
-gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
-gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2 h1:zz/4sD22gZqvAdBgd6gYLRIbvrgoQLDE7emPK0sXC6o=
-gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
+gitlab.com/gitlab-org/gitaly/v15 v15.2.0 h1:UCv8LhzG6dCaq2zFsQqDuFVqFua4KR0+DKyx7MFx7kA=
+gitlab.com/gitlab-org/gitaly/v15 v15.2.0/go.mod h1:WjitFL44l9ovitGC4OvSuGwfeq0VpHUbHS6sDw13LV8=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed h1:aXSyBpG6K/QsTGevZnpFoDR7Nwvn24RpkDoWe37B8eY=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
-gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
-gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
-gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
-gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
+gitlab.com/gitlab-org/labkit v1.15.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
 gitlab.com/gitlab-org/labkit v1.16.0 h1:Vm3NAMZ8RqAunXlvPWby3GJ2R35vsYGP6Uu0YjyMIlY=
 gitlab.com/gitlab-org/labkit v1.16.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
-go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
-go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
-go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -917,20 +811,23 @@
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
-go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
+go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
 go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
 go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
-go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
-gocloud.dev v0.23.0/go.mod h1:zklCCIIo1N9ELkU2S2E7tW8P8eeMU7oGLeQCXdDwx9Q=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+gocloud.dev v0.25.0/go.mod h1:7HegHVCYZrMiU3IE1qtnzf/vRrDwLYnRNR3EhWX8x9Y=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -948,7 +845,6 @@
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -960,7 +856,6 @@
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@@ -973,16 +868,13 @@
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -995,7 +887,6 @@
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -1005,7 +896,6 @@
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -1023,13 +913,19 @@
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
+golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA=
+golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1042,12 +938,16 @@
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a h1:4Kd8OPUx1xgUwrHDaviWZO8MsgoZTZYC3g+8m16RBww=
 golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM=
+golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1058,16 +958,14 @@
 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1083,27 +981,22 @@
 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1122,20 +1015,14 @@
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1144,13 +1031,29 @@
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
+golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU=
+golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1161,18 +1064,16 @@
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
-golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
+golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -1194,11 +1095,9 @@
 golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -1213,6 +1112,7 @@
 golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
@@ -1238,7 +1138,6 @@
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1252,13 +1151,10 @@
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
-google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
@@ -1277,31 +1173,41 @@
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
-google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA=
 google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
 google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
 google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
 google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
 google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
-google.golang.org/api v0.54.0 h1:ECJUVngj71QI6XEm7b1sAf8BljU5inEhMbKPR8Lxhhk=
 google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
+google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
+google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E=
+google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
+google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
+google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
+google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM=
+google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M=
+google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
+google.golang.org/api v0.68.0/go.mod h1:sOM8pTpwgflXRhz+oC8H2Dr+UcbMqkPPWNJo88Q7TH8=
+google.golang.org/api v0.69.0/go.mod h1:boanBiw+h5c3s+tBPgEzLDRHfFLWV0qXxRHz3ws7C80=
+google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
+google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
+google.golang.org/api v0.74.0 h1:ExR2D+5TYIrMphWgs5JCgwRhEDlPDXXrLwHHMgPHTXE=
+google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
@@ -1341,12 +1247,9 @@
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
-google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210420162539-3c870d7478d2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210517163617-5e0236093d7a/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
@@ -1355,19 +1258,43 @@
 google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
 google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
 google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
-google.golang.org/genproto v0.0.0-20210813162853-db860fec028c h1:iLQakcwWG3k/++1q/46apVb1sUQ3IqIdn9yUE6eh/xA=
 google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
-google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
-google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
+google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220204002441-d6cc3cc0770e/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220216160803-4663080d8bc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
+google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de h1:9Ti5SG2U4cAcluryUo/sFay3TQKoxiFMfaT0pbizU7k=
+google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
@@ -1388,8 +1315,12 @@
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
 google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
-google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
 google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
+google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
+google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -1403,10 +1334,9 @@
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-gopkg.in/DataDog/dd-trace-go.v1 v1.7.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
-gopkg.in/DataDog/dd-trace-go.v1 v1.30.0/go.mod h1:SnKViq44dv/0gjl9RpkP0Y2G3BJSRkp6eYdCSu39iI8=
+google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 h1:DkD0plWEVUB8v/Ru6kRBW30Hy/fRNBC8hPdcExuBZMc=
 gopkg.in/DataDog/dd-trace-go.v1 v1.32.0/go.mod h1:wRKMf/tRASHwH/UOfPQ3IQmVFhTz2/1a1/mpXoIjF54=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
@@ -1416,28 +1346,17 @@
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
-gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
-gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
 gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
 gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
 gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
-gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
-gopkg.in/jcmturner/goidentity.v2 v2.0.0/go.mod h1:vCwK9HeXksMeUmQ4SxDd1tRz4LejrKh3KRVjQWhjvZI=
-gopkg.in/jcmturner/gokrb5.v5 v5.3.0/go.mod h1:oQz8Wc5GsctOTgCVyKad1Vw4TCWz5G6gfIQr88RPv4k=
-gopkg.in/jcmturner/rpc.v0 v0.0.2/go.mod h1:NzMq6cRzR9lipgw7WxRBHNx5N8SifBuaCQsOT1kWY/E=
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1448,9 +1367,10 @@
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1458,11 +1378,8 @@
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
 nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
-sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -5,8 +5,8 @@
 
 	"google.golang.org/grpc"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -5,8 +5,8 @@
 
 	"google.golang.org/grpc"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -5,8 +5,8 @@
 
 	"google.golang.org/grpc"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
--- a/internal/gitaly/gitaly.go
+++ b/internal/gitaly/gitaly.go
@@ -9,9 +9,9 @@
 	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
 	"google.golang.org/grpc"
 
-	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	gitalyclient "gitlab.com/gitlab-org/gitaly/v14/client"
+	gitalyauth "gitlab.com/gitlab-org/gitaly/v15/auth"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	gitalyclient "gitlab.com/gitlab-org/gitaly/v15/client"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
 	"gitlab.com/gitlab-org/labkit/log"
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -5,7 +5,7 @@
 	"fmt"
 	"net/http"
 
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -10,7 +10,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -16,7 +16,7 @@
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -11,7 +11,7 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1660093449 0
#      Wed Aug 10 01:04:09 2022 +0000
# Node ID 2a1bdd3f4889f2e86839bd8f44e6980e34ec599f
# Parent  2d47cb0b0251d461399adca678da9819011b2c04
# Parent  8c20e6acc5d7a3192b2b124dfbfcf80728fd9a36
Merge branch 'id-update-gitaly-to-v15' into 'main'

Update Gitaly to v15

See merge request gitlab-org/gitlab-shell!676

diff --git a/client/testserver/gitalyserver.go b/client/testserver/gitalyserver.go
--- a/client/testserver/gitalyserver.go
+++ b/client/testserver/gitalyserver.go
@@ -10,8 +10,8 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/labkit/log"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials/insecure"
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -22,8 +22,8 @@
 	"github.com/mikesmitty/edkey"
 	"github.com/pires/go-proxyproto"
 	"github.com/stretchr/testify/require"
-	gitalyClient "gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	gitalyClient "gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 	"golang.org/x/crypto/ssh"
 )
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -10,27 +10,29 @@
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.2
-	github.com/prometheus/client_golang v1.12.1
+	github.com/prometheus/client_golang v1.12.2
 	github.com/sirupsen/logrus v1.8.1
-	github.com/stretchr/testify v1.7.0
-	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
+	github.com/stretchr/testify v1.8.0
+	gitlab.com/gitlab-org/gitaly/v15 v15.2.0
 	gitlab.com/gitlab-org/labkit v1.16.0
-	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
-	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	google.golang.org/grpc v1.40.0
+	golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
+	golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
+	google.golang.org/grpc v1.47.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
 require (
-	cloud.google.com/go v0.92.2 // indirect
+	cloud.google.com/go v0.100.2 // indirect
+	cloud.google.com/go/compute v1.5.0 // indirect
+	cloud.google.com/go/monitoring v1.4.0 // indirect
 	cloud.google.com/go/profiler v0.1.0 // indirect
-	cloud.google.com/go/trace v0.1.0 // indirect
-	contrib.go.opencensus.io/exporter/stackdriver v0.13.8 // indirect
+	cloud.google.com/go/trace v1.2.0 // indirect
+	contrib.go.opencensus.io/exporter/stackdriver v0.13.10 // indirect
 	github.com/DataDog/datadog-go v4.4.0+incompatible // indirect
 	github.com/DataDog/sketches-go v1.0.0 // indirect
 	github.com/Microsoft/go-winio v0.5.0 // indirect
 	github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
-	github.com/aws/aws-sdk-go v1.38.35 // indirect
+	github.com/aws/aws-sdk-go v1.43.31 // indirect
 	github.com/beevik/ntp v0.3.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
@@ -41,12 +43,13 @@
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
-	github.com/google/go-cmp v0.5.6 // indirect
+	github.com/google/go-cmp v0.5.8 // indirect
 	github.com/google/pprof v0.0.0-20210804190019-f964ff605595 // indirect
-	github.com/google/uuid v1.2.0 // indirect
-	github.com/googleapis/gax-go/v2 v2.0.5 // indirect
-	github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 // indirect
+	github.com/google/uuid v1.3.0 // indirect
+	github.com/googleapis/gax-go/v2 v2.2.0 // indirect
+	github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	github.com/kr/text v0.2.0 // indirect
 	github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 // indirect
 	github.com/lightstep/lightstep-tracer-go v0.25.0 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
@@ -63,22 +66,28 @@
 	github.com/tinylib/msgp v1.1.2 // indirect
 	github.com/tklauser/go-sysconf v0.3.4 // indirect
 	github.com/tklauser/numcpus v0.2.1 // indirect
-	github.com/uber/jaeger-client-go v2.29.1+incompatible // indirect
+	github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
 	github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
 	go.opencensus.io v0.23.0 // indirect
-	go.uber.org/atomic v1.7.0 // indirect
-	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
-	golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a // indirect
-	golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
+	go.uber.org/atomic v1.9.0 // indirect
+	golang.org/x/net v0.0.0-20220531201128-c960675eff93 // indirect
+	golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
+	golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
 	golang.org/x/text v0.3.7 // indirect
-	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
+	golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
-	google.golang.org/api v0.54.0 // indirect
+	google.golang.org/api v0.74.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20210813162853-db860fec028c // indirect
-	google.golang.org/protobuf v1.27.1 // indirect
+	google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de // indirect
+	google.golang.org/protobuf v1.28.0 // indirect
 	gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
+	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
+// Gitaly specifies Gitlab Shell as a dependency and causes older versions
+// of Gitaly and Gitlab Shell to be included in go.sum.
+// Let's exclude the older version of Gitlab Shell to break the chain in the beginning
+exclude gitlab.com/gitlab-org/gitlab-shell/v14 v14.8.0
+
 replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -1,9 +1,6 @@
-bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
-bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
 cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
@@ -22,104 +19,118 @@
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
 cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.82.0/go.mod h1:vlKccHJGuFBFufnAnuB08dfEH9Y3H7dzDzRECFdC2TA=
 cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
 cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
 cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
 cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
-cloud.google.com/go v0.92.2 h1:podK44+0gcW5rWGMjJiPH0+rzkCTQx/zT0qF5CLqVkM=
 cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
+cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
+cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
+cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U=
+cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y=
+cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
+cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw=
+cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
+cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM=
+cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo=
+cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
+cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=
+cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw=
+cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc=
+cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
+cloud.google.com/go/kms v1.1.0/go.mod h1:WdbppnCDMDpOvoYBMn1+gNmOeEoZYqAv+HeuKARGCXI=
+cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA=
+cloud.google.com/go/monitoring v1.1.0/go.mod h1:L81pzz7HKn14QCMaCs6NTQkdBnE87TElyanS95vIcl4=
+cloud.google.com/go/monitoring v1.4.0 h1:05+IuNMbh40hbxcqQ4SnynbwZbLG1Wc9dysIJxnfv7U=
+cloud.google.com/go/monitoring v1.4.0/go.mod h1:y6xnxfwI3hTFWOdkOaD7nfJVlwuC3/mS/5kvtT131p4=
 cloud.google.com/go/profiler v0.1.0 h1:MG/rxKC1MztRfEWMGYKFISxyZak5hNh29f0A/z2tvWk=
 cloud.google.com/go/profiler v0.1.0/go.mod h1:D7S7LV/zKbRWkOzYL1b5xytpqt8Ikd/v/yvf1/Tx2pQ=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
 cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
-cloud.google.com/go/pubsub v1.10.3/go.mod h1:FUcc28GpGxxACoklPsE1sCtbkY4Ix+ro7yvw+h82Jn4=
+cloud.google.com/go/pubsub v1.19.0/go.mod h1:/O9kmSe9bb9KRnIAWkzmqhPjHo6LtzGOBYd/kr06XSs=
+cloud.google.com/go/secretmanager v1.3.0/go.mod h1:+oLTkouyiYiabAQNugCeTS3PAArGiMJuBqvJnJsyH+U=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-cloud.google.com/go/storage v1.15.0 h1:Ljj+ZXVEhCr/1+4ZhvtteN1ND7UUsNTlduGclLh8GO0=
-cloud.google.com/go/storage v1.15.0/go.mod h1:mjjQMoxxyGH7Jr8K5qrx6N2O0AHsczI61sMNn03GIZI=
-cloud.google.com/go/trace v0.1.0 h1:nUGUK79FOkN0UGUXhBmVBkbu1PYsHe0YyFSPLOD9Npg=
+cloud.google.com/go/storage v1.21.0 h1:HwnT2u2D309SFDHQII6m18HlrCi3jAXhUMTLOWXYH14=
+cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKuqnZI01LAA=
 cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g=
+cloud.google.com/go/trace v1.0.0/go.mod h1:4iErSByzxkyHWzzlAj63/Gmjz0NH1ASqhJguHpGcr6A=
+cloud.google.com/go/trace v1.2.0 h1:oIaB4KahkIUOpLSAAjEJ8y2desbjY/x/RfP4O3KAtTI=
+cloud.google.com/go/trace v1.2.0/go.mod h1:Wc8y/uYyOhPy12KEnXG9XGrvfMz5F5SrYecQlbW1rwM=
 contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
-contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
-contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.10 h1:a9+GZPUe+ONKUwULjlEOucMMG0qfSCCenlji0Nhqbys=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.10/go.mod h1:I5htMbyta491eUxufwwZPQdcKvvgzMB4O9ni41YnIM8=
 contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
-github.com/Azure/azure-amqp-common-go/v3 v3.1.0/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0=
+github.com/Azure/azure-amqp-common-go/v3 v3.2.1/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI=
+github.com/Azure/azure-amqp-common-go/v3 v3.2.2/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI=
 github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
 github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
-github.com/Azure/azure-sdk-for-go v54.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
-github.com/Azure/azure-service-bus-go v0.10.11/go.mod h1:AWw9eTTWZVZyvgpPahD1ybz3a8/vT3GsJDS8KYex55U=
-github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nUdBD+e64lEuc4sVnuOfNs=
-github.com/Azure/go-amqp v0.13.0/go.mod h1:qj+o8xPCz9tMSbQ83Vp8boHahuRDl5mkNHyt1xlxUTs=
-github.com/Azure/go-amqp v0.13.4/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
-github.com/Azure/go-amqp v0.13.7/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
+github.com/Azure/azure-sdk-for-go v59.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
+github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
+github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU=
+github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck=
+github.com/Azure/go-amqp v0.16.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
+github.com/Azure/go-amqp v0.16.4/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
 github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
-github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
 github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
-github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
-github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE=
+github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
+github.com/Azure/go-autorest/autorest v0.11.22/go.mod h1:BAWYUWGPEtKPzjVkp0Q6an0MJcJDsoh5Z1BFAEFs4Xs=
 github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
-github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
 github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
-github.com/Azure/go-autorest/autorest/azure/auth v0.5.7/go.mod h1:AkzUsqkrdmNhfP2i54HqINVQopw0CLDnvHpJ88Zz1eI=
+github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
+github.com/Azure/go-autorest/autorest/adal v0.9.17/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.9/go.mod h1:hg3/1yw0Bq87O3KvvnJoAh34/0zbP7SFizX/qN5JvjU=
 github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
 github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
-github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
 github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
 github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
 github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
-github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
 github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
-github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w=
 github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
 github.com/DataDog/datadog-go v4.4.0+incompatible h1:R7WqXWP4fIOAqWJtUKmSfuc7eDsBT58k9AY5WSHVosk=
 github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
 github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
 github.com/DataDog/sketches-go v1.0.0 h1:chm5KSXO7kO+ywGWJ0Zs6tdmWU8PBXSbywFVciL6BG4=
 github.com/DataDog/sketches-go v1.0.0/go.mod h1:O+XkJHWk9w4hDwY2ZUDU31ZC9sNYlYo8DiFsxjYeo1k=
-github.com/GoogleCloudPlatform/cloudsql-proxy v1.22.0/go.mod h1:mAm5O/zik2RFmcpigNjg6nMotDL8ZXJaxKzgGVcSMFA=
-github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
+github.com/GoogleCloudPlatform/cloudsql-proxy v1.29.0/go.mod h1:spvB9eLJH9dutlbPSRmHvSXXHOwGRyeXh1jVdquA2G8=
 github.com/HdrHistogram/hdrhistogram-go v1.1.1 h1:cJXY5VLMHgejurPjZH6Fo9rIwRGLefBGdiaENZALqrg=
 github.com/HdrHistogram/hdrhistogram-go v1.1.1/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
-github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
-github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
-github.com/Microsoft/go-winio v0.4.19/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
 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/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
-github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
-github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 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=
-github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
-github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
 github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
@@ -128,40 +139,52 @@
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
-github.com/alexbrainman/sspi v0.0.0-20180125232955-4729b3d4d858/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
+github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
-github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
-github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
-github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
-github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
 github.com/avast/retry-go v2.4.2+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
-github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
+github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
-github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
-github.com/aws/aws-sdk-go v1.38.35 h1:7AlAO0FC+8nFjxiGKEmq0QLpiA8/XFr6eIxgRTwkdTg=
-github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
-github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
+github.com/aws/aws-sdk-go v1.43.31 h1:yJZIr8nMV1hXjAvvOLUFqZRJcHV7udPQBfhJqawDzI0=
+github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
+github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM=
+github.com/aws/aws-sdk-go-v2/config v1.15.3/go.mod h1:9YL3v07Xc/ohTsxFXzan9ZpFpdTOFl4X65BAKYaz8jg=
+github.com/aws/aws-sdk-go-v2/credentials v1.11.2/go.mod h1:j8YsY9TXTm31k4eFhspiQicfXPLZ0gYXA50i4gxPE8g=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3/go.mod h1:uk1vhHHERfSVCUnqSqz8O48LBYDSC+k6brng09jcMOk=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3/go.mod h1:0dHuD2HZZSiwfJSy1FO5bX1hQ1TxVV1QXXjpn3XUE44=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9/go.mod h1:AnVH5pvai0pAF4lXRq0bmhbes1u9R8wTE+g+183bZNM=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10/go.mod h1:8DcYQcz0+ZJaSxANlHIsbbi6S+zMwjwdDqwW3r9AzaE=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3/go.mod h1:Seb8KNmD6kVTjwRjVEgOT5hPin6sq+v4C2ycJQDwuH8=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3/go.mod h1:wlY6SVjuwvh3TVRpTqdy4I1JpBFLX4UGeKZdWntaocw=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3/go.mod h1:Bm/v2IaN6rZ+Op7zX+bOUMdL4fsrYZiD0dsjLhNKwZc=
+github.com/aws/aws-sdk-go-v2/service/kms v1.16.3/go.mod h1:QuiHPBqlOFCi4LqdSskYYAWpQlx3PKmohy+rE2F+o5g=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z4KJ7DtFFCx8fJD2a491Ak=
+github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4/go.mod h1:PJc8s+lxyU8rrre0/4a0pn2wgwiDvOEzoOjcJUBr67o=
+github.com/aws/aws-sdk-go-v2/service/sns v1.17.4/go.mod h1:kElt+uCcXxcqFyc+bQqZPFD9DME/eC6oHBXvFzQ9Bcw=
+github.com/aws/aws-sdk-go-v2/service/sqs v1.18.3/go.mod h1:skmQo0UPvsjsuYYSYMVmrPc1HWCbHUJyrCEp+ZaLzqM=
+github.com/aws/aws-sdk-go-v2/service/ssm v1.24.1/go.mod h1:NR/xoKjdbRJ+qx0pMR4mI+N/H1I1ynHwXnO6FowXJc0=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.3/go.mod h1:7UQ/e69kU7LDPtY40OyoHYgRmgfGM4mgsLYtcObdveU=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.3/go.mod h1:bfBj0iVmsUyUg4weDB4NxktD9rDGeKSVWnjTnwbx9b8=
+github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
 github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
 github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 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/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
-github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 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=
-github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
 github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@@ -171,63 +194,54 @@
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
-github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
+github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
 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/tableflip v0.0.0-20190329062924-8392f1641731/go.mod h1:erh4dYezoMVbIa52pi7i1Du7+cXOgqNuTamt10qvMoA=
-github.com/cloudflare/tableflip v1.2.2 h1:WkhiowHlg0nZuH7Y2beLVIZDfxtSvKta1f22PEgUN7w=
-github.com/cloudflare/tableflip v1.2.2/go.mod h1:P4gRehmV6Z2bY5ao5ml9Pd8u6kuEnlB37pUFMmv7j2E=
+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=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
 github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
-github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
-github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
-github.com/containerd/cgroups v0.0.0-20201118023556-2819c83ced99/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
+github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
-github.com/coreos/go-systemd/v22 v22.3.1/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
-github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
-github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU=
 github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
 github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
 github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
 github.com/dgryski/go-minhash v0.0.0-20170608043002-7fe510aff544/go.mod h1:VBi0XHpFy0xiMySf6YpVbRqrupW4RprJ5QTyN+XvGSM=
 github.com/dgryski/go-spooky v0.0.0-20170606183049-ed3d087f40e2/go.mod h1:hgHYKsoIw7S/hlWtP7wD1wZ7SX1jPTtKko5X9jrOgPQ=
 github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
 github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
+github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/dpotapov/go-spnego v0.0.0-20190506202455-c2c609116ad0/go.mod h1:P4f4MSk7h52F2PK0lCapn5+fu47Uf8aRdxDSqgezxZE=
-github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dpotapov/go-spnego v0.0.0-20210315154721-298b63a54430/go.mod h1:AVSs/gZKt1bOd2AhkhbS7Qh56Hv7klde22yXVbwYJhc=
+github.com/dpotapov/go-spnego v0.0.0-20220426193508-b7f82e4507db/go.mod h1:AVSs/gZKt1bOd2AhkhbS7Qh56Hv7klde22yXVbwYJhc=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
-github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
-github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
-github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
 github.com/ekzhu/minhash-lsh v0.0.0-20171225071031-5c06ee8586a1/go.mod h1:yEtCVi+QamvzjEH4U/m6ZGkALIkF2xfQnFp0BcKmIOk=
 github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
-github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -235,41 +249,37 @@
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
-github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
-github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
-github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
-github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
+github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
-github.com/getsentry/raven-go v0.1.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
-github.com/getsentry/raven-go v0.1.2/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
-github.com/getsentry/sentry-go v0.5.1/go.mod h1:B8H7x8TYDPkeWPRzGpIiFO97LZP6rL8A3hEt8lUItMw=
-github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
-github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
 github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
-github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
 github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
+github.com/gin-gonic/gin v1.7.3/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
 github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
-github.com/git-lfs/git-lfs v1.5.1-0.20210304194248-2e1d981afbe3/go.mod h1:8Xqs4mqL7o6xEnaXckIgELARTeK7RYtm3pBab7S79Js=
-github.com/git-lfs/gitobj/v2 v2.0.1/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
-github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
-github.com/git-lfs/wildmatch v1.0.4/go.mod h1:SdHAGnApDpnFYQ0vAxbniWR0sn7yLJ3QXo9RRfhn2ew=
+github.com/git-lfs/git-lfs/v3 v3.2.0/go.mod h1:GZZO3jw2Yn3/1KFV4nRoXUzH+yPzGypIdTeQpkzxEvQ=
+github.com/git-lfs/gitobj/v2 v2.1.0/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
+github.com/git-lfs/go-netrc v0.0.0-20210914205454-f0c862dd687a/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
+github.com/git-lfs/pktline v0.0.0-20210330133718-06e9096e2825/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
+github.com/git-lfs/wildmatch/v2 v2.0.1/go.mod h1:EVqonpk9mXbREP3N8UkwoWdrF249uHpCUo5CPXY81gw=
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
+github.com/go-enry/go-enry/v2 v2.8.2/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ=
 github.com/go-enry/go-license-detector/v4 v4.3.0/go.mod h1:HaM4wdNxSlz/9Gw0uVOKSQS5JVFqf2Pk8xUPEn6bldI=
+github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
 github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
@@ -284,7 +294,6 @@
 github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@@ -297,7 +306,6 @@
 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
-github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -309,30 +317,25 @@
 github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
 github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
-github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
-github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
 github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
 github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
 github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@@ -361,7 +364,6 @@
 github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -377,11 +379,13 @@
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
-github.com/google/go-replayers/grpcreplay v1.0.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
-github.com/google/go-replayers/httpreplay v0.1.2/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
+github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk=
+github.com/google/go-replayers/httpreplay v1.1.1/go.mod h1:gN9GeLIs7l6NUoVaSSnv2RiqK1NiwAmD0MrKeC9IIks=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -390,6 +394,7 @@
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
+github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -403,6 +408,7 @@
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210125172800-10e9aeb4a998/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
@@ -411,69 +417,50 @@
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
-github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
 github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
+github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
+github.com/googleapis/gax-go/v2 v2.2.0 h1:s7jOdKSaksJVOxE0Y/S32otcfiP+UQ0cL8/GTKaONwE=
+github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
-github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-middleware v1.2.3-0.20210213123510-be4c235f9d1c/go.mod h1:RXwzibsL7UhPcEmGyGvXKJ8kyJsOCOEaLgGce4igMFs=
 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
-github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
+github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
-github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
 github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
-github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
-github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
-github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
-github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
-github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
-github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
-github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
-github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 h1:Y4V+SFe7d3iH+9pJCoeWIOS5/xBJIFsltS7E+KJSsJY=
-github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
+github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I=
+github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
 github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b/go.mod h1:HmaZGXHdSwQh1jnUlBGN2BeEYOHACLVGzYOXCbsLvxY=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
 github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
-github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
 github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
 github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
 github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
@@ -486,7 +473,8 @@
 github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
 github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
 github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
-github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono=
 github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
 github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
 github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
@@ -500,39 +488,42 @@
 github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
 github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
 github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
 github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
 github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
-github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
 github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
 github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
 github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
-github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
+github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
+github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ=
 github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
-github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
+github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
 github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
+github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
+github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 github.com/jdkato/prose v1.1.0/go.mod h1:jkF0lkxaX5PFSlk9l4Gh9Y+T57TqUZziWT7uZbW5ADg=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
 github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
-github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -541,34 +532,23 @@
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jteeuwen/go-bindata v3.0.8-0.20180305030458-6025e8de665b+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
-github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
-github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
-github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
-github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk=
 github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
-github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U=
 github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
-github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw=
 github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
-github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
 github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
 github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
-github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
+github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
 github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
-github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -582,29 +562,21 @@
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
+github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
 github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/leonelquinteros/gotext v1.5.0/go.mod h1:OCiUVHuhP9LGFBQ1oAmdtNCHJCiHiQA8lf4nAifHkr0=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
-github.com/libgit2/git2go/v31 v31.4.12/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec=
+github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go/v33 v33.0.9/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
-github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
-github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7/go.mod h1:Spd59icnvRxSKuyijbbwe5AemzvcyXAUBgApa7VybMw=
-github.com/lightstep/lightstep-tracer-go v0.15.6/go.mod h1:6AMpwZpsyCFwSovxzM78e+AsYxE8sGwiM6C3TytaWeI=
-github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/lightstep/lightstep-tracer-go v0.24.0/go.mod h1:RnONwHKg89zYPmF+Uig5PpHMUcYCFgml8+r4SS53y7A=
 github.com/lightstep/lightstep-tracer-go v0.25.0 h1:sGVnz8h3jTQuHKMbUe2949nXm3Sg09N1UcR3VoQNN5E=
 github.com/lightstep/lightstep-tracer-go v0.25.0/go.mod h1:G1ZAEaqTHFPWpWunnbUn1ADEY/Jvzz7jIOaXwAfD6A8=
-github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
@@ -613,6 +585,7 @@
 github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
+github.com/mattn/go-ieproxy v0.0.3/go.mod h1:6ZpRmhBaYuBX1U2za+9rC9iCGLsSp2tftelZne7CPko=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@@ -621,59 +594,44 @@
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
-github.com/mattn/go-shellwords v0.0.0-20190425161501-2444a32a19f4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-shellwords v1.0.11 h1:vCoR9VPpsk/TZFW2JwK5I9S0xdrtUq2bph6/YjEPnaw=
 github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
 github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg=
-github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
 github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
 github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
-github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
 github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
-github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
-github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
-github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
-github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
 github.com/montanaflynn/stats v0.0.0-20151014174947-eeaced052adb/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
-github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
-github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
-github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
 github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
-github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
 github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
-github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
-github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc=
 github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
-github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -682,86 +640,58 @@
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
-github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
-github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
 github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
-github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
-github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
-github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
-github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
-github.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc=
 github.com/otiai10/copy v1.4.2 h1:RTiz2sol3eoXPLF4o+YWqEybwfUa/Q2Nkc4ZIUs3fwI=
 github.com/otiai10/copy v1.4.2/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E=
 github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
 github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
-github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw=
 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
 github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E=
 github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
-github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
-github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
-github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
-github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
-github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
 github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
-github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
-github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
-github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
 github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
 github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
+github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
+github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -774,19 +704,13 @@
 github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086/go.mod h1:YpdgDXpumPB/+EGmGTYHeiW/0QVFRzBYTNFaxWfPDk4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
-github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
-github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/sebest/xff v0.0.0-20160910043805-6c115e0ffa35/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
-github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY=
-github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
 github.com/shirou/gopsutil/v3 v3.21.2 h1:fIOk3hyqV1oGKogfGNjUZa0lUbtlkx3+ZT0IoJth2uM=
 github.com/shirou/gopsutil/v3 v3.21.2/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
 github.com/shogo82148/go-shuffle v0.0.0-20170808115208-59829097ff3b/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
@@ -795,7 +719,6 @@
 github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
@@ -804,58 +727,45 @@
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/ssgelm/cookiejarparser v1.0.1/go.mod h1:DUfC0mpjIzlDN7DzKjXpHj0qMI5m9VrZuz3wSlI+OEI=
-github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
-github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
-github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
-github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
 github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=
 github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
 github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc=
 github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
-github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-client-go v2.27.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-client-go v2.29.1+incompatible h1:R9ec3zO3sGpzs0abd43Y+fBZRJ9uiH6lXyR/+u6brW4=
 github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
+github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
+github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
 github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
 github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
-github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
-github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
-github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
@@ -868,12 +778,10 @@
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v0.0.0-20170210233622-6b67b3fab74d/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
 github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
 github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
-github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -881,28 +789,14 @@
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
-gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
-gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
-gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
-gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059 h1:X7+3GQIxUpScXpIMCU5+sfpYvZyBIQ3GMlEosP7Jssw=
-gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
-gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
-gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2 h1:zz/4sD22gZqvAdBgd6gYLRIbvrgoQLDE7emPK0sXC6o=
-gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
+gitlab.com/gitlab-org/gitaly/v15 v15.2.0 h1:UCv8LhzG6dCaq2zFsQqDuFVqFua4KR0+DKyx7MFx7kA=
+gitlab.com/gitlab-org/gitaly/v15 v15.2.0/go.mod h1:WjitFL44l9ovitGC4OvSuGwfeq0VpHUbHS6sDw13LV8=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed h1:aXSyBpG6K/QsTGevZnpFoDR7Nwvn24RpkDoWe37B8eY=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
-gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
-gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
-gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
-gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
+gitlab.com/gitlab-org/labkit v1.15.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
 gitlab.com/gitlab-org/labkit v1.16.0 h1:Vm3NAMZ8RqAunXlvPWby3GJ2R35vsYGP6Uu0YjyMIlY=
 gitlab.com/gitlab-org/labkit v1.16.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
-go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
-go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
-go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -917,20 +811,23 @@
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
-go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
+go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
 go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
 go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
-go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
-gocloud.dev v0.23.0/go.mod h1:zklCCIIo1N9ELkU2S2E7tW8P8eeMU7oGLeQCXdDwx9Q=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+gocloud.dev v0.25.0/go.mod h1:7HegHVCYZrMiU3IE1qtnzf/vRrDwLYnRNR3EhWX8x9Y=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -948,7 +845,6 @@
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -960,7 +856,6 @@
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@@ -973,16 +868,13 @@
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -995,7 +887,6 @@
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -1005,7 +896,6 @@
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -1023,13 +913,19 @@
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
+golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA=
+golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1042,12 +938,16 @@
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a h1:4Kd8OPUx1xgUwrHDaviWZO8MsgoZTZYC3g+8m16RBww=
 golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM=
+golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1058,16 +958,14 @@
 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1083,27 +981,22 @@
 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1122,20 +1015,14 @@
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1144,13 +1031,29 @@
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
+golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU=
+golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1161,18 +1064,16 @@
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
-golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
+golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -1194,11 +1095,9 @@
 golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -1213,6 +1112,7 @@
 golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
@@ -1238,7 +1138,6 @@
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1252,13 +1151,10 @@
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
-google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
@@ -1277,31 +1173,41 @@
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
-google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA=
 google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
 google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
 google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
 google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
 google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
-google.golang.org/api v0.54.0 h1:ECJUVngj71QI6XEm7b1sAf8BljU5inEhMbKPR8Lxhhk=
 google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
+google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
+google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E=
+google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
+google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
+google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
+google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM=
+google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M=
+google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
+google.golang.org/api v0.68.0/go.mod h1:sOM8pTpwgflXRhz+oC8H2Dr+UcbMqkPPWNJo88Q7TH8=
+google.golang.org/api v0.69.0/go.mod h1:boanBiw+h5c3s+tBPgEzLDRHfFLWV0qXxRHz3ws7C80=
+google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
+google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
+google.golang.org/api v0.74.0 h1:ExR2D+5TYIrMphWgs5JCgwRhEDlPDXXrLwHHMgPHTXE=
+google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
@@ -1341,12 +1247,9 @@
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
-google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210420162539-3c870d7478d2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210517163617-5e0236093d7a/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
@@ -1355,19 +1258,43 @@
 google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
 google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
 google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
-google.golang.org/genproto v0.0.0-20210813162853-db860fec028c h1:iLQakcwWG3k/++1q/46apVb1sUQ3IqIdn9yUE6eh/xA=
 google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
-google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
-google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
+google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220204002441-d6cc3cc0770e/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220216160803-4663080d8bc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
+google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de h1:9Ti5SG2U4cAcluryUo/sFay3TQKoxiFMfaT0pbizU7k=
+google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
@@ -1388,8 +1315,12 @@
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
 google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
-google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
 google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
+google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
+google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -1403,10 +1334,9 @@
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-gopkg.in/DataDog/dd-trace-go.v1 v1.7.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
-gopkg.in/DataDog/dd-trace-go.v1 v1.30.0/go.mod h1:SnKViq44dv/0gjl9RpkP0Y2G3BJSRkp6eYdCSu39iI8=
+google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 h1:DkD0plWEVUB8v/Ru6kRBW30Hy/fRNBC8hPdcExuBZMc=
 gopkg.in/DataDog/dd-trace-go.v1 v1.32.0/go.mod h1:wRKMf/tRASHwH/UOfPQ3IQmVFhTz2/1a1/mpXoIjF54=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
@@ -1416,28 +1346,17 @@
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
-gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
-gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
 gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
 gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
 gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
-gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
-gopkg.in/jcmturner/goidentity.v2 v2.0.0/go.mod h1:vCwK9HeXksMeUmQ4SxDd1tRz4LejrKh3KRVjQWhjvZI=
-gopkg.in/jcmturner/gokrb5.v5 v5.3.0/go.mod h1:oQz8Wc5GsctOTgCVyKad1Vw4TCWz5G6gfIQr88RPv4k=
-gopkg.in/jcmturner/rpc.v0 v0.0.2/go.mod h1:NzMq6cRzR9lipgw7WxRBHNx5N8SifBuaCQsOT1kWY/E=
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1448,9 +1367,10 @@
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1458,11 +1378,8 @@
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
 nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
-sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -5,8 +5,8 @@
 
 	"google.golang.org/grpc"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -5,8 +5,8 @@
 
 	"google.golang.org/grpc"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -5,8 +5,8 @@
 
 	"google.golang.org/grpc"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
--- a/internal/gitaly/gitaly.go
+++ b/internal/gitaly/gitaly.go
@@ -9,9 +9,9 @@
 	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
 	"google.golang.org/grpc"
 
-	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	gitalyclient "gitlab.com/gitlab-org/gitaly/v14/client"
+	gitalyauth "gitlab.com/gitlab-org/gitaly/v15/auth"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	gitalyclient "gitlab.com/gitlab-org/gitaly/v15/client"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
 	"gitlab.com/gitlab-org/labkit/log"
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -5,7 +5,7 @@
 	"fmt"
 	"net/http"
 
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -10,7 +10,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -16,7 +16,7 @@
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -11,7 +11,7 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1660213275 -7200
#      Thu Aug 11 12:21:15 2022 +0200
# Node ID cee9648a5338e046e2ecd8caf04ebf46ff9a10bc
# Parent  2a1bdd3f4889f2e86839bd8f44e6980e34ec599f
Release 14.11.0

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v14.11.0
+
+- Update Gitaly to v15 !676
+- Fixed extra slashes in API request paths generated for geo !673
+
 v14.10.0
 
 - Implement Push Auth support for 2FA verification !454
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.10.0
+14.11.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1660213681 0
#      Thu Aug 11 10:28:01 2022 +0000
# Node ID 24d75d667bfd829693f80c59f4fccd9c395995a0
# Parent  2a1bdd3f4889f2e86839bd8f44e6980e34ec599f
# Parent  cee9648a5338e046e2ecd8caf04ebf46ff9a10bc
Merge branch 'id-release-14-11-0' into 'main'

Release 14.11.0

See merge request gitlab-org/gitlab-shell!677

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v14.11.0
+
+- Update Gitaly to v15 !676
+- Fixed extra slashes in API request paths generated for geo !673
+
 v14.10.0
 
 - Implement Push Auth support for 2FA verification !454
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.10.0
+14.11.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1660214019 -7200
#      Thu Aug 11 12:33:39 2022 +0200
# Node ID c7e54c8e62b50aec0616b02ae5424c5c869e72cd
# Parent  2a1bdd3f4889f2e86839bd8f44e6980e34ec599f
Update gitlab-dangerfiles gem

diff --git a/Gemfile b/Gemfile
--- a/Gemfile
+++ b/Gemfile
@@ -5,5 +5,5 @@
 end
 
 group :development, :danger do
-  gem 'gitlab-dangerfiles', '~> 3.0.0'
+  gem 'gitlab-dangerfiles', '~> 3.5.1'
 end
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -11,7 +11,7 @@
     colored2 (3.1.2)
     cork (0.3.0)
       colored2 (~> 3.1)
-    danger (8.5.0)
+    danger (8.6.1)
       claide (~> 1.0)
       claide-plugins (>= 0.9.2)
       colored2 (~> 3.1)
@@ -28,7 +28,7 @@
       danger
       gitlab (~> 4.2, >= 4.2.0)
     diff-lcs (1.3)
-    faraday (1.10.0)
+    faraday (1.10.1)
       faraday-em_http (~> 1.0)
       faraday-em_synchrony (~> 1.0)
       faraday-excon (~> 1.1)
@@ -43,29 +43,29 @@
     faraday-em_http (1.0.0)
     faraday-em_synchrony (1.0.0)
     faraday-excon (1.1.0)
-    faraday-http-cache (2.2.0)
+    faraday-http-cache (2.4.1)
       faraday (>= 0.8)
     faraday-httpclient (1.0.1)
-    faraday-multipart (1.0.3)
-      multipart-post (>= 1.2, < 3)
+    faraday-multipart (1.0.4)
+      multipart-post (~> 2)
     faraday-net_http (1.0.1)
     faraday-net_http_persistent (1.2.0)
     faraday-patron (1.0.0)
     faraday-rack (1.0.0)
     faraday-retry (1.0.3)
-    git (1.10.2)
+    git (1.11.0)
       rchardet (~> 1.8)
-    gitlab (4.18.0)
-      httparty (~> 0.18)
+    gitlab (4.19.0)
+      httparty (~> 0.20)
       terminal-table (>= 1.5.1)
-    gitlab-dangerfiles (3.0.0)
+    gitlab-dangerfiles (3.5.1)
       danger (>= 8.4.5)
       danger-gitlab (>= 8.0.0)
       rake
     httparty (0.20.0)
       mime-types (~> 3.0)
       multi_xml (>= 0.5.2)
-    kramdown (2.3.2)
+    kramdown (2.4.0)
       rexml
     kramdown-parser-gfm (1.1.0)
       kramdown (~> 2.0)
@@ -73,14 +73,14 @@
       mime-types-data (~> 3.2015)
     mime-types-data (3.2022.0105)
     multi_xml (0.6.0)
-    multipart-post (2.1.1)
+    multipart-post (2.2.3)
     nap (1.1.0)
     no_proxy_fix (0.1.2)
-    octokit (4.22.0)
-      faraday (>= 0.9)
-      sawyer (~> 0.8.0, >= 0.5.3)
+    octokit (4.25.1)
+      faraday (>= 1, < 3)
+      sawyer (~> 0.9)
     open4 (1.3.4)
-    public_suffix (4.0.6)
+    public_suffix (4.0.7)
     rake (13.0.6)
     rchardet (1.8.0)
     rexml (3.2.5)
@@ -98,18 +98,18 @@
       rspec-support (~> 3.8.0)
     rspec-support (3.8.0)
     ruby2_keywords (0.0.5)
-    sawyer (0.8.2)
+    sawyer (0.9.2)
       addressable (>= 2.3.5)
-      faraday (> 0.8, < 2.0)
+      faraday (>= 0.17.3, < 3)
     terminal-table (3.0.2)
       unicode-display_width (>= 1.1.1, < 3)
-    unicode-display_width (2.1.0)
+    unicode-display_width (2.2.0)
 
 PLATFORMS
   ruby
 
 DEPENDENCIES
-  gitlab-dangerfiles (~> 3.0.0)
+  gitlab-dangerfiles (~> 3.5.1)
   rspec (~> 3.8.0)
 
 BUNDLED WITH
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1660214492 0
#      Thu Aug 11 10:41:32 2022 +0000
# Node ID da67507aa519217f382c8f8c4fdbcf947741cae3
# Parent  24d75d667bfd829693f80c59f4fccd9c395995a0
# Parent  c7e54c8e62b50aec0616b02ae5424c5c869e72cd
Merge branch 'id-update-danger-gem' into 'main'

Update gitlab-dangerfiles gem

See merge request gitlab-org/gitlab-shell!678

diff --git a/Gemfile b/Gemfile
--- a/Gemfile
+++ b/Gemfile
@@ -5,5 +5,5 @@
 end
 
 group :development, :danger do
-  gem 'gitlab-dangerfiles', '~> 3.0.0'
+  gem 'gitlab-dangerfiles', '~> 3.5.1'
 end
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -11,7 +11,7 @@
     colored2 (3.1.2)
     cork (0.3.0)
       colored2 (~> 3.1)
-    danger (8.5.0)
+    danger (8.6.1)
       claide (~> 1.0)
       claide-plugins (>= 0.9.2)
       colored2 (~> 3.1)
@@ -28,7 +28,7 @@
       danger
       gitlab (~> 4.2, >= 4.2.0)
     diff-lcs (1.3)
-    faraday (1.10.0)
+    faraday (1.10.1)
       faraday-em_http (~> 1.0)
       faraday-em_synchrony (~> 1.0)
       faraday-excon (~> 1.1)
@@ -43,29 +43,29 @@
     faraday-em_http (1.0.0)
     faraday-em_synchrony (1.0.0)
     faraday-excon (1.1.0)
-    faraday-http-cache (2.2.0)
+    faraday-http-cache (2.4.1)
       faraday (>= 0.8)
     faraday-httpclient (1.0.1)
-    faraday-multipart (1.0.3)
-      multipart-post (>= 1.2, < 3)
+    faraday-multipart (1.0.4)
+      multipart-post (~> 2)
     faraday-net_http (1.0.1)
     faraday-net_http_persistent (1.2.0)
     faraday-patron (1.0.0)
     faraday-rack (1.0.0)
     faraday-retry (1.0.3)
-    git (1.10.2)
+    git (1.11.0)
       rchardet (~> 1.8)
-    gitlab (4.18.0)
-      httparty (~> 0.18)
+    gitlab (4.19.0)
+      httparty (~> 0.20)
       terminal-table (>= 1.5.1)
-    gitlab-dangerfiles (3.0.0)
+    gitlab-dangerfiles (3.5.1)
       danger (>= 8.4.5)
       danger-gitlab (>= 8.0.0)
       rake
     httparty (0.20.0)
       mime-types (~> 3.0)
       multi_xml (>= 0.5.2)
-    kramdown (2.3.2)
+    kramdown (2.4.0)
       rexml
     kramdown-parser-gfm (1.1.0)
       kramdown (~> 2.0)
@@ -73,14 +73,14 @@
       mime-types-data (~> 3.2015)
     mime-types-data (3.2022.0105)
     multi_xml (0.6.0)
-    multipart-post (2.1.1)
+    multipart-post (2.2.3)
     nap (1.1.0)
     no_proxy_fix (0.1.2)
-    octokit (4.22.0)
-      faraday (>= 0.9)
-      sawyer (~> 0.8.0, >= 0.5.3)
+    octokit (4.25.1)
+      faraday (>= 1, < 3)
+      sawyer (~> 0.9)
     open4 (1.3.4)
-    public_suffix (4.0.6)
+    public_suffix (4.0.7)
     rake (13.0.6)
     rchardet (1.8.0)
     rexml (3.2.5)
@@ -98,18 +98,18 @@
       rspec-support (~> 3.8.0)
     rspec-support (3.8.0)
     ruby2_keywords (0.0.5)
-    sawyer (0.8.2)
+    sawyer (0.9.2)
       addressable (>= 2.3.5)
-      faraday (> 0.8, < 2.0)
+      faraday (>= 0.17.3, < 3)
     terminal-table (3.0.2)
       unicode-display_width (>= 1.1.1, < 3)
-    unicode-display_width (2.1.0)
+    unicode-display_width (2.2.0)
 
 PLATFORMS
   ruby
 
 DEPENDENCIES
-  gitlab-dangerfiles (~> 3.0.0)
+  gitlab-dangerfiles (~> 3.5.1)
   rspec (~> 3.8.0)
 
 BUNDLED WITH
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1660644259 -7200
#      Tue Aug 16 12:04:19 2022 +0200
# Node ID 6082a294c6e49bfa043d3513880418798a96a444
# Parent  da67507aa519217f382c8f8c4fdbcf947741cae3
Add Golang 1.19 to CI

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -61,7 +61,7 @@
   image: golang:${GO_VERSION}
   parallel:
     matrix:
-      - GO_VERSION: ["1.17", "1.18"]
+      - GO_VERSION: ["1.17", "1.18", "1.19"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1660654815 0
#      Tue Aug 16 13:00:15 2022 +0000
# Node ID 588a8deae1853c4812d941298db4d6faca3afc49
# Parent  da67507aa519217f382c8f8c4fdbcf947741cae3
# Parent  6082a294c6e49bfa043d3513880418798a96a444
Merge branch 'id-test-against-1.19' into 'main'

Run tests agains 1.19 Golang

See merge request gitlab-org/gitlab-shell!680

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -61,7 +61,7 @@
   image: golang:${GO_VERSION}
   parallel:
     matrix:
-      - GO_VERSION: ["1.17", "1.18"]
+      - GO_VERSION: ["1.17", "1.18", "1.19"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1661862904 -7200
#      Tue Aug 30 14:35:04 2022 +0200
# Node ID df2c3f6bd21886d287c305c11b17fbb380e4da7e
# Parent  588a8deae1853c4812d941298db4d6faca3afc49
Update Gitaly to 15.4.0-rc2

It has gitlab-shell dependency removed, so it makes sense to
remove the exclusion of circular dependency

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -11,13 +11,13 @@
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.2
 	github.com/prometheus/client_golang v1.12.2
-	github.com/sirupsen/logrus v1.8.1
+	github.com/sirupsen/logrus v1.9.0
 	github.com/stretchr/testify v1.8.0
-	gitlab.com/gitlab-org/gitaly/v15 v15.2.0
+	gitlab.com/gitlab-org/gitaly/v15 v15.4.0-rc2
 	gitlab.com/gitlab-org/labkit v1.16.0
-	golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
+	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
 	golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
-	google.golang.org/grpc v1.47.0
+	google.golang.org/grpc v1.48.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
@@ -47,7 +47,7 @@
 	github.com/google/pprof v0.0.0-20210804190019-f964ff605595 // indirect
 	github.com/google/uuid v1.3.0 // indirect
 	github.com/googleapis/gax-go/v2 v2.2.0 // indirect
-	github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect
+	github.com/hashicorp/yamux v0.1.1 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 // indirect
@@ -72,22 +72,17 @@
 	go.uber.org/atomic v1.9.0 // indirect
 	golang.org/x/net v0.0.0-20220531201128-c960675eff93 // indirect
 	golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
-	golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
+	golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	google.golang.org/api v0.74.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de // indirect
-	google.golang.org/protobuf v1.28.0 // indirect
+	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // indirect
 	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
-// Gitaly specifies Gitlab Shell as a dependency and causes older versions
-// of Gitaly and Gitlab Shell to be included in go.sum.
-// Let's exclude the older version of Gitlab Shell to break the chain in the beginning
-exclude gitlab.com/gitlab-org/gitlab-shell/v14 v14.8.0
-
 replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -45,6 +45,7 @@
 cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
 cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
 cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=
 cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw=
@@ -122,12 +123,16 @@
 github.com/HdrHistogram/hdrhistogram-go v1.1.1 h1:cJXY5VLMHgejurPjZH6Fo9rIwRGLefBGdiaENZALqrg=
 github.com/HdrHistogram/hdrhistogram-go v1.1.1/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
+github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
 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/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=
@@ -142,7 +147,9 @@
 github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/avast/retry-go v2.4.2+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
@@ -182,6 +189,8 @@
 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/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
+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=
@@ -198,6 +207,7 @@
 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/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=
@@ -215,16 +225,18 @@
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU=
 github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
 github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
@@ -261,6 +273,7 @@
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
 github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
 github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
@@ -291,6 +304,7 @@
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY=
 github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@@ -307,17 +321,17 @@
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
-github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
-github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
-github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
-github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
+github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs=
+github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY=
+github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc=
 github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
 github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE=
 github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@@ -439,23 +453,40 @@
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
 github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
 github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I=
-github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
+github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
 github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b/go.mod h1:HmaZGXHdSwQh1jnUlBGN2BeEYOHACLVGzYOXCbsLvxY=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
@@ -474,7 +505,7 @@
 github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
 github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
 github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
-github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono=
+github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
 github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
 github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
 github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
@@ -488,20 +519,20 @@
 github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
 github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
 github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
 github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
 github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
 github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
-github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
 github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
 github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
 github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
 github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
-github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ=
+github.com/jackc/pgx/v4 v4.17.0/go.mod h1:Gd6RmOhtFLTu8cp/Fhq4kP195KrshxYJH3oW8AWJ1pw=
 github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
@@ -535,6 +566,7 @@
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
+github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
 github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
 github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
 github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
@@ -553,6 +585,8 @@
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
@@ -570,6 +604,7 @@
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go/v33 v33.0.9/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
@@ -578,6 +613,10 @@
 github.com/lightstep/lightstep-tracer-go v0.25.0 h1:sGVnz8h3jTQuHKMbUe2949nXm3Sg09N1UcR3VoQNN5E=
 github.com/lightstep/lightstep-tracer-go v0.25.0/go.mod h1:G1ZAEaqTHFPWpWunnbUn1ADEY/Jvzz7jIOaXwAfD6A8=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
+github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=
+github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -594,23 +633,34 @@
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-shellwords v1.0.11 h1:vCoR9VPpsk/TZFW2JwK5I9S0xdrtUq2bph6/YjEPnaw=
 github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
-github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
 github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
 github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@@ -629,8 +679,6 @@
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc=
 github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
-github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
-github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -652,23 +700,28 @@
 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
 github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E=
 github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml/v2 v2.0.3/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
 github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
 github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
 github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
 github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
@@ -693,20 +746,20 @@
 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
 github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
 github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
-github.com/rubenv/sql-migrate v0.0.0-20191213152630-06338513c237/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc=
+github.com/rubenv/sql-migrate v1.1.2/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ=
 github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086/go.mod h1:YpdgDXpumPB/+EGmGTYHeiW/0QVFRzBYTNFaxWfPDk4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@@ -723,19 +776,25 @@
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
 github.com/ssgelm/cookiejarparser v1.0.1/go.mod h1:DUfC0mpjIzlDN7DzKjXpHj0qMI5m9VrZuz3wSlI+OEI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -752,6 +811,7 @@
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
 github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=
@@ -787,15 +847,18 @@
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
-gitlab.com/gitlab-org/gitaly/v15 v15.2.0 h1:UCv8LhzG6dCaq2zFsQqDuFVqFua4KR0+DKyx7MFx7kA=
-gitlab.com/gitlab-org/gitaly/v15 v15.2.0/go.mod h1:WjitFL44l9ovitGC4OvSuGwfeq0VpHUbHS6sDw13LV8=
+gitlab.com/gitlab-org/gitaly/v15 v15.4.0-rc2 h1:ngV/NWEMz4TRlnJFzBpatdXkEIDMHsU3YUzGdZU63qY=
+gitlab.com/gitlab-org/gitaly/v15 v15.4.0-rc2/go.mod h1:Rv/LoDU5I3cOP6KLUWFjC1eigcay2u8b8ZcsW86A0Xo=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed h1:aXSyBpG6K/QsTGevZnpFoDR7Nwvn24RpkDoWe37B8eY=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-gitlab.com/gitlab-org/labkit v1.15.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
 gitlab.com/gitlab-org/labkit v1.16.0 h1:Vm3NAMZ8RqAunXlvPWby3GJ2R35vsYGP6Uu0YjyMIlY=
 gitlab.com/gitlab-org/labkit v1.16.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
+go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
+go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -826,8 +889,9 @@
 go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
 go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
-gocloud.dev v0.25.0/go.mod h1:7HegHVCYZrMiU3IE1qtnzf/vRrDwLYnRNR3EhWX8x9Y=
+gocloud.dev v0.26.0/go.mod h1:mkUgejbnbLotorqDyvedJO20XcZNTynmSeVSQS9btVg=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -872,7 +936,9 @@
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -916,6 +982,7 @@
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -938,6 +1005,7 @@
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
@@ -965,6 +1033,7 @@
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -975,7 +1044,6 @@
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -983,6 +1051,7 @@
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1031,6 +1100,7 @@
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1049,8 +1119,8 @@
 golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU=
-golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1094,10 +1164,10 @@
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -1139,6 +1209,7 @@
 golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1173,6 +1244,7 @@
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
+google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
 google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
 google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
 google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
@@ -1319,8 +1391,8 @@
 google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
-google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
-google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w=
+google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -1335,8 +1407,9 @@
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 h1:DkD0plWEVUB8v/Ru6kRBW30Hy/fRNBC8hPdcExuBZMc=
 gopkg.in/DataDog/dd-trace-go.v1 v1.32.0/go.mod h1:wRKMf/tRASHwH/UOfPQ3IQmVFhTz2/1a1/mpXoIjF54=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
@@ -1349,9 +1422,9 @@
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
 gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
 gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1661926633 0
#      Wed Aug 31 06:17:13 2022 +0000
# Node ID 2640fced7a7dd0d0a80a54598e441c666a60906d
# Parent  588a8deae1853c4812d941298db4d6faca3afc49
# Parent  df2c3f6bd21886d287c305c11b17fbb380e4da7e
Merge branch 'id-bump-gitaly' into 'main'

Update Gitaly to 15.4.0-rc2

See merge request gitlab-org/gitlab-shell!681

diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -11,13 +11,13 @@
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.2
 	github.com/prometheus/client_golang v1.12.2
-	github.com/sirupsen/logrus v1.8.1
+	github.com/sirupsen/logrus v1.9.0
 	github.com/stretchr/testify v1.8.0
-	gitlab.com/gitlab-org/gitaly/v15 v15.2.0
+	gitlab.com/gitlab-org/gitaly/v15 v15.4.0-rc2
 	gitlab.com/gitlab-org/labkit v1.16.0
-	golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
+	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
 	golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
-	google.golang.org/grpc v1.47.0
+	google.golang.org/grpc v1.48.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
@@ -47,7 +47,7 @@
 	github.com/google/pprof v0.0.0-20210804190019-f964ff605595 // indirect
 	github.com/google/uuid v1.3.0 // indirect
 	github.com/googleapis/gax-go/v2 v2.2.0 // indirect
-	github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect
+	github.com/hashicorp/yamux v0.1.1 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 // indirect
@@ -72,22 +72,17 @@
 	go.uber.org/atomic v1.9.0 // indirect
 	golang.org/x/net v0.0.0-20220531201128-c960675eff93 // indirect
 	golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
-	golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
+	golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	google.golang.org/api v0.74.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de // indirect
-	google.golang.org/protobuf v1.28.0 // indirect
+	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // indirect
 	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
-// Gitaly specifies Gitlab Shell as a dependency and causes older versions
-// of Gitaly and Gitlab Shell to be included in go.sum.
-// Let's exclude the older version of Gitlab Shell to break the chain in the beginning
-exclude gitlab.com/gitlab-org/gitlab-shell/v14 v14.8.0
-
 replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -45,6 +45,7 @@
 cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
 cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
 cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=
 cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw=
@@ -122,12 +123,16 @@
 github.com/HdrHistogram/hdrhistogram-go v1.1.1 h1:cJXY5VLMHgejurPjZH6Fo9rIwRGLefBGdiaENZALqrg=
 github.com/HdrHistogram/hdrhistogram-go v1.1.1/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
+github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
 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/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=
@@ -142,7 +147,9 @@
 github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/avast/retry-go v2.4.2+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
@@ -182,6 +189,8 @@
 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/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
+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=
@@ -198,6 +207,7 @@
 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/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=
@@ -215,16 +225,18 @@
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU=
 github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
 github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
@@ -261,6 +273,7 @@
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
 github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
 github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
@@ -291,6 +304,7 @@
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY=
 github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@@ -307,17 +321,17 @@
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
-github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
-github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
-github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
-github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
+github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs=
+github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY=
+github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc=
 github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
 github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE=
 github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@@ -439,23 +453,40 @@
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
 github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
 github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I=
-github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
+github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
 github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b/go.mod h1:HmaZGXHdSwQh1jnUlBGN2BeEYOHACLVGzYOXCbsLvxY=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
@@ -474,7 +505,7 @@
 github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
 github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
 github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
-github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono=
+github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
 github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
 github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
 github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
@@ -488,20 +519,20 @@
 github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
 github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
 github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
 github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
 github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
 github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
-github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
 github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
 github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
 github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
 github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
-github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ=
+github.com/jackc/pgx/v4 v4.17.0/go.mod h1:Gd6RmOhtFLTu8cp/Fhq4kP195KrshxYJH3oW8AWJ1pw=
 github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
@@ -535,6 +566,7 @@
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
+github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
 github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
 github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
 github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
@@ -553,6 +585,8 @@
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
@@ -570,6 +604,7 @@
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go/v33 v33.0.9/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
@@ -578,6 +613,10 @@
 github.com/lightstep/lightstep-tracer-go v0.25.0 h1:sGVnz8h3jTQuHKMbUe2949nXm3Sg09N1UcR3VoQNN5E=
 github.com/lightstep/lightstep-tracer-go v0.25.0/go.mod h1:G1ZAEaqTHFPWpWunnbUn1ADEY/Jvzz7jIOaXwAfD6A8=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
+github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=
+github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -594,23 +633,34 @@
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-shellwords v1.0.11 h1:vCoR9VPpsk/TZFW2JwK5I9S0xdrtUq2bph6/YjEPnaw=
 github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
-github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
 github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
 github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@@ -629,8 +679,6 @@
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc=
 github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
-github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
-github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -652,23 +700,28 @@
 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
 github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E=
 github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml/v2 v2.0.3/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
 github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
 github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
 github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
 github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
@@ -693,20 +746,20 @@
 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
 github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
 github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
-github.com/rubenv/sql-migrate v0.0.0-20191213152630-06338513c237/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc=
+github.com/rubenv/sql-migrate v1.1.2/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ=
 github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086/go.mod h1:YpdgDXpumPB/+EGmGTYHeiW/0QVFRzBYTNFaxWfPDk4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@@ -723,19 +776,25 @@
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
 github.com/ssgelm/cookiejarparser v1.0.1/go.mod h1:DUfC0mpjIzlDN7DzKjXpHj0qMI5m9VrZuz3wSlI+OEI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -752,6 +811,7 @@
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
 github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=
@@ -787,15 +847,18 @@
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
-gitlab.com/gitlab-org/gitaly/v15 v15.2.0 h1:UCv8LhzG6dCaq2zFsQqDuFVqFua4KR0+DKyx7MFx7kA=
-gitlab.com/gitlab-org/gitaly/v15 v15.2.0/go.mod h1:WjitFL44l9ovitGC4OvSuGwfeq0VpHUbHS6sDw13LV8=
+gitlab.com/gitlab-org/gitaly/v15 v15.4.0-rc2 h1:ngV/NWEMz4TRlnJFzBpatdXkEIDMHsU3YUzGdZU63qY=
+gitlab.com/gitlab-org/gitaly/v15 v15.4.0-rc2/go.mod h1:Rv/LoDU5I3cOP6KLUWFjC1eigcay2u8b8ZcsW86A0Xo=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed h1:aXSyBpG6K/QsTGevZnpFoDR7Nwvn24RpkDoWe37B8eY=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-gitlab.com/gitlab-org/labkit v1.15.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
 gitlab.com/gitlab-org/labkit v1.16.0 h1:Vm3NAMZ8RqAunXlvPWby3GJ2R35vsYGP6Uu0YjyMIlY=
 gitlab.com/gitlab-org/labkit v1.16.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
+go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
+go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -826,8 +889,9 @@
 go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
 go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
-gocloud.dev v0.25.0/go.mod h1:7HegHVCYZrMiU3IE1qtnzf/vRrDwLYnRNR3EhWX8x9Y=
+gocloud.dev v0.26.0/go.mod h1:mkUgejbnbLotorqDyvedJO20XcZNTynmSeVSQS9btVg=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -872,7 +936,9 @@
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -916,6 +982,7 @@
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -938,6 +1005,7 @@
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
@@ -965,6 +1033,7 @@
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -975,7 +1044,6 @@
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -983,6 +1051,7 @@
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1031,6 +1100,7 @@
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1049,8 +1119,8 @@
 golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU=
-golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1094,10 +1164,10 @@
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -1139,6 +1209,7 @@
 golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1173,6 +1244,7 @@
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
+google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
 google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
 google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
 google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
@@ -1319,8 +1391,8 @@
 google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
-google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
-google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w=
+google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -1335,8 +1407,9 @@
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 h1:DkD0plWEVUB8v/Ru6kRBW30Hy/fRNBC8hPdcExuBZMc=
 gopkg.in/DataDog/dd-trace-go.v1 v1.32.0/go.mod h1:wRKMf/tRASHwH/UOfPQ3IQmVFhTz2/1a1/mpXoIjF54=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
@@ -1349,9 +1422,9 @@
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
 gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
 gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
# HG changeset patch
# User Georges Racinet on purity.racinet.fr <georges@racinet.fr>
# Date 1663174194 -7200
#      Wed Sep 14 18:49:54 2022 +0200
# Branch heptapod
# Node ID 2daff2db282c6fbf244ff5d8e2b386bc8253f60d
# Parent  57a0260a6fb85a23d5d983b8bf04030cc1b79768
# Parent  7b17b1d00467952ade430e1753f00ff2b92a303c
Merged upstream v14.0.0 into Heptapod Shell

Used in intermediate upstream GitLab revisions between 14.10
and 15.0.

Notes:

- upstream drops Golang 1.16 from CI, we're doing the same
- filled in Heptapod changelog

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -60,7 +60,7 @@
   image: golang:${GO_VERSION}
   parallel:
     matrix:
-      - GO_VERSION: ["1.16", "1.17", "1.18"]
+      - GO_VERSION: ["1.17", "1.18"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.8
+golang 1.17.9
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,17 @@
+v14.0.0
+- Always use Gitaly sidechannel connections !567
+
+v13.26.0
+
+- Add JWT token to GitLab Rails request !596
+- Drop go 1.16 support !601
+- Remove `self_signed_cert` option !602
+
+v13.25.2
+
+- Revert "Abort long-running unauthenticated SSH connections" !605
+- Bump Go to 1.17.9 for asdf users !600
+
 v13.25.1
 
 - Upgrade golang to 1.17.8 !591
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 14.0.0
+
+- Jump to upstream GitLab Shell v14.0.0 (pre-GitLab 15.0)
+
 ## 13.25.0
 
 - Jump to upstream GitLab Shell v13.25.1 (GitLab 14.10 series)
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.25.1
+14.0.0
diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -10,13 +10,19 @@
 	"path"
 	"strings"
 	"testing"
+	"time"
 
+	"github.com/golang-jwt/jwt/v4"
 	"github.com/stretchr/testify/require"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
 
+var (
+	secret = []byte("sssh, it's a secret")
+)
+
 func TestClients(t *testing.T) {
 	testhelper.PrepareTestRootDir(t)
 
@@ -57,12 +63,10 @@
 		t.Run(tc.desc, func(t *testing.T) {
 			url := tc.server(t, buildRequests(t, tc.relativeURLRoot))
 
-			secret := "sssh, it's a secret"
-
-			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", false, 1, nil)
+			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", 1, nil)
 			require.NoError(t, err)
 
-			client, err := NewGitlabNetClient("", "", secret, httpClient)
+			client, err := NewGitlabNetClient("", "", string(secret), httpClient)
 			require.NoError(t, err)
 
 			testBrokenRequest(t, client)
@@ -71,6 +75,7 @@
 			testMissing(t, client)
 			testErrorMessage(t, client)
 			testAuthenticationHeader(t, client)
+			testJWTAuthenticationHeader(t, client)
 		})
 	}
 }
@@ -160,7 +165,7 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, "sssh, it's a secret", string(header))
+		require.Equal(t, secret, header)
 	})
 
 	t.Run("Authentication headers for POST", func(t *testing.T) {
@@ -175,7 +180,44 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, "sssh, it's a secret", string(header))
+		require.Equal(t, secret, header)
+	})
+}
+
+func testJWTAuthenticationHeader(t *testing.T, client *GitlabNetClient) {
+	verifyJWTToken := func(t *testing.T, response *http.Response) {
+		responseBody, err := io.ReadAll(response.Body)
+		require.NoError(t, err)
+
+		claims := &jwt.RegisteredClaims{}
+		token, err := jwt.ParseWithClaims(string(responseBody), claims, func(token *jwt.Token) (interface{}, error) {
+			return secret, nil
+		})
+		require.NoError(t, err)
+		require.True(t, token.Valid)
+		require.Equal(t, "gitlab-shell", claims.Issuer)
+		require.Equal(t, time.Now().Truncate(time.Second), claims.IssuedAt.Time, time.Second)
+		require.Equal(t, time.Now().Truncate(time.Second).Add(time.Minute), claims.ExpiresAt.Time, time.Second)
+	}
+
+	t.Run("JWT authentication headers for GET", func(t *testing.T) {
+		response, err := client.Get(context.Background(), "/jwt_auth")
+		require.NoError(t, err)
+		require.NotNil(t, response)
+
+		defer response.Body.Close()
+
+		verifyJWTToken(t, response)
+	})
+
+	t.Run("JWT authentication headers for POST", func(t *testing.T) {
+		response, err := client.Post(context.Background(), "/jwt_auth", map[string]string{})
+		require.NoError(t, err)
+		require.NotNil(t, response)
+
+		defer response.Body.Close()
+
+		verifyJWTToken(t, response)
 	})
 }
 
@@ -209,6 +251,12 @@
 			},
 		},
 		{
+			Path: "/api/v4/internal/jwt_auth",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				fmt.Fprint(w, r.Header.Get(apiSecretHeaderName))
+			},
+		},
+		{
 			Path: "/api/v4/internal/error",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -11,13 +11,18 @@
 	"strings"
 	"time"
 
+	"github.com/golang-jwt/jwt/v4"
+
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
-	internalApiPath  = "/api/v4/internal"
-	secretHeaderName = "Gitlab-Shared-Secret"
-	defaultUserAgent = "GitLab-Shell"
+	internalApiPath     = "/api/v4/internal"
+	secretHeaderName    = "Gitlab-Shared-Secret"
+	apiSecretHeaderName = "Gitlab-Shell-Api-Request"
+	defaultUserAgent    = "GitLab-Shell"
+	jwtTTL              = time.Minute
+	jwtIssuer           = "gitlab-shell"
 )
 
 type ErrorResponse struct {
@@ -121,10 +126,22 @@
 	if user != "" && password != "" {
 		request.SetBasicAuth(user, password)
 	}
+	secretBytes := []byte(c.secret)
 
-	encodedSecret := base64.StdEncoding.EncodeToString([]byte(c.secret))
+	encodedSecret := base64.StdEncoding.EncodeToString(secretBytes)
 	request.Header.Set(secretHeaderName, encodedSecret)
 
+	claims := jwt.RegisteredClaims{
+		Issuer:    jwtIssuer,
+		IssuedAt:  jwt.NewNumericDate(time.Now()),
+		ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtTTL)),
+	}
+	tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secretBytes)
+	if err != nil {
+		return nil, err
+	}
+	request.Header.Set(apiSecretHeaderName, tokenString)
+
 	request.Header.Add("Content-Type", "application/json")
 	request.Header.Add("User-Agent", c.userAgent)
 	request.Close = true
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -14,7 +14,6 @@
 	"time"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
-	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/tracing"
 )
 
@@ -70,17 +69,8 @@
 	return nil
 }
 
-// Deprecated: use NewHTTPClientWithOpts - https://gitlab.com/gitlab-org/gitlab-shell/-/issues/484
-func NewHTTPClient(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64) *HttpClient {
-	c, err := NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath, selfSignedCert, readTimeoutSeconds, nil)
-	if err != nil {
-		log.WithError(err).Error("new http client with opts")
-	}
-	return c
-}
-
 // NewHTTPClientWithOpts builds an HTTP client using the provided options
-func NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64, opts []HTTPClientOpt) (*HttpClient, error) {
+func NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, readTimeoutSeconds uint64, opts []HTTPClientOpt) (*HttpClient, error) {
 	var transport *http.Transport
 	var host string
 	var err error
@@ -103,7 +93,7 @@
 			opt(hcc)
 		}
 
-		transport, host, err = buildHttpsTransport(*hcc, selfSignedCert, gitlabURL)
+		transport, host, err = buildHttpsTransport(*hcc, gitlabURL)
 		if err != nil {
 			return nil, err
 		}
@@ -140,7 +130,7 @@
 	return transport, host
 }
 
-func buildHttpsTransport(hcc httpClientCfg, selfSignedCert bool, gitlabURL string) (*http.Transport, string, error) {
+func buildHttpsTransport(hcc httpClientCfg, gitlabURL string) (*http.Transport, string, error) {
 	certPool, err := x509.SystemCertPool()
 
 	if err != nil {
@@ -162,12 +152,8 @@
 		}
 	}
 	tlsConfig := &tls.Config{
-		RootCAs: certPool,
-		// The self_signed_cert config setting is deprecated
-		// The field and its usage is going to be removed in
-		// https://gitlab.com/gitlab-org/gitlab-shell/-/issues/541
-		InsecureSkipVerify: selfSignedCert,
-		MinVersion:         tls.VersionTLS12,
+		RootCAs:    certPool,
+		MinVersion: tls.VersionTLS12,
 	}
 
 	if hcc.HaveCertAndKey() {
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -17,7 +17,7 @@
 func TestReadTimeout(t *testing.T) {
 	expectedSeconds := uint64(300)
 
-	client, err := NewHTTPClientWithOpts("http://localhost:3000", "", "", "", false, expectedSeconds, nil)
+	client, err := NewHTTPClientWithOpts("http://localhost:3000", "", "", "", expectedSeconds, nil)
 	require.NoError(t, err)
 
 	require.NotNil(t, client)
@@ -123,7 +123,7 @@
 func setup(t *testing.T, username, password string, requests []testserver.TestRequestHandler) *GitlabNetClient {
 	url := testserver.StartHttpServer(t, requests)
 
-	httpClient, err := NewHTTPClientWithOpts(url, "", "", "", false, 1, nil)
+	httpClient, err := NewHTTPClientWithOpts(url, "", "", "", 1, nil)
 	require.NoError(t, err)
 
 	client, err := NewGitlabNetClient(username, password, "", httpClient)
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -18,7 +18,6 @@
 	testCases := []struct {
 		desc                                        string
 		caFile, caPath                              string
-		selfSigned                                  bool
 		clientCAPath, clientCertPath, clientKeyPath string // used for TLS client certs
 	}{
 		{
@@ -31,9 +30,8 @@
 			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
 		},
 		{
-			desc:       "Invalid cert with self signed cert option enabled",
-			caFile:     path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
-			selfSigned: true,
+			desc:   "Invalid cert with self signed cert option enabled",
+			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
 		},
 		{
 			desc:   "Client certs with CA",
@@ -48,7 +46,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client, err := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath, tc.selfSigned)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath)
 			require.NoError(t, err)
 
 			response, err := client.Get(context.Background(), "/hello")
@@ -95,7 +93,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client, err := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "", false)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "")
 			if tc.expectedCaFileNotFound {
 				require.Error(t, err)
 				require.ErrorIs(t, err, ErrCafileNotFound)
@@ -109,7 +107,7 @@
 	}
 }
 
-func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string, selfSigned bool) (*GitlabNetClient, error) {
+func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string) (*GitlabNetClient, error) {
 	testhelper.PrepareTestRootDir(t)
 
 	requests := []testserver.TestRequestHandler{
@@ -130,7 +128,7 @@
 		opts = append(opts, WithClientCert(clientCertPath, clientKeyPath))
 	}
 
-	httpClient, err := NewHTTPClientWithOpts(url, "", caFile, caPath, selfSigned, 1, opts)
+	httpClient, err := NewHTTPClientWithOpts(url, "", caFile, caPath, 1, opts)
 	if err != nil {
 		return nil, err
 	}
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -43,11 +43,6 @@
 #  ca_file: /etc/ssl/cert.pem
 #  ca_path: /etc/pki/tls/certs
 #
-#  The self_signed_cert option is deprecated
-#  When it's set to true, any certificate is accepted, which may make machine-in-the-middle attack possible
-#  Certificates specified in ca_file and ca_path are trusted anyway even if they are self-signed
-#  Issue: https://gitlab.com/gitlab-org/gitlab-shell/-/issues/120
-  self_signed_cert: false
 
 # File used as authorized_keys for gitlab user
 auth_file: "/home/git/.ssh/authorized_keys"
@@ -100,8 +95,6 @@
   readiness_probe: "/start"
   # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
   liveness_probe: "/health"
-  # The server disconnects after this time (in seconds) if the user has not successfully logged in. Defaults to 60.
-  login_grace_time: 60
   # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -1,8 +1,9 @@
 module gitlab.com/gitlab-org/gitlab-shell
 
-go 1.16
+go 1.17
 
 require (
+	github.com/golang-jwt/jwt/v4 v4.4.1
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
 	github.com/mattn/go-shellwords v1.0.11
@@ -19,4 +20,65 @@
 	gopkg.in/yaml.v2 v2.4.0
 )
 
+require (
+	cloud.google.com/go v0.92.2 // indirect
+	cloud.google.com/go/profiler v0.1.0 // indirect
+	cloud.google.com/go/trace v0.1.0 // indirect
+	contrib.go.opencensus.io/exporter/stackdriver v0.13.8 // indirect
+	github.com/DataDog/datadog-go v4.4.0+incompatible // indirect
+	github.com/DataDog/sketches-go v1.0.0 // indirect
+	github.com/Microsoft/go-winio v0.5.0 // indirect
+	github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
+	github.com/aws/aws-sdk-go v1.38.35 // indirect
+	github.com/beevik/ntp v0.3.0 // indirect
+	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
+	github.com/cespare/xxhash/v2 v2.1.1 // indirect
+	github.com/client9/reopen v1.0.0 // indirect
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/go-ole/go-ole v1.2.4 // indirect
+	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/google/go-cmp v0.5.6 // indirect
+	github.com/google/pprof v0.0.0-20210804190019-f964ff605595 // indirect
+	github.com/google/uuid v1.2.0 // indirect
+	github.com/googleapis/gax-go/v2 v2.0.5 // indirect
+	github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 // indirect
+	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 // indirect
+	github.com/lightstep/lightstep-tracer-go v0.25.0 // indirect
+	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
+	github.com/oklog/ulid/v2 v2.0.2 // indirect
+	github.com/opentracing/opentracing-go v1.2.0 // indirect
+	github.com/philhofer/fwd v1.1.1 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/prometheus/client_model v0.2.0 // indirect
+	github.com/prometheus/common v0.26.0 // indirect
+	github.com/prometheus/procfs v0.6.0 // indirect
+	github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect
+	github.com/shirou/gopsutil/v3 v3.21.2 // indirect
+	github.com/sirupsen/logrus v1.8.1 // indirect
+	github.com/tinylib/msgp v1.1.2 // indirect
+	github.com/tklauser/go-sysconf v0.3.4 // indirect
+	github.com/tklauser/numcpus v0.2.1 // indirect
+	github.com/uber/jaeger-client-go v2.29.1+incompatible // indirect
+	github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
+	go.opencensus.io v0.23.0 // indirect
+	go.uber.org/atomic v1.7.0 // indirect
+	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
+	golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a // indirect
+	golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 // indirect
+	golang.org/x/text v0.3.6 // indirect
+	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
+	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+	google.golang.org/api v0.54.0 // indirect
+	google.golang.org/appengine v1.6.7 // indirect
+	google.golang.org/genproto v0.0.0-20210813162853-db860fec028c // indirect
+	google.golang.org/protobuf v1.27.1 // indirect
+	gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // indirect
+	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
+)
+
 replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -316,6 +316,8 @@
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
+github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
diff --git a/heptapod-ci.yml b/heptapod-ci.yml
--- a/heptapod-ci.yml
+++ b/heptapod-ci.yml
@@ -43,7 +43,7 @@
   image: golang:${GO_VERSION}
   parallel:
     matrix:
-      - GO_VERSION: ["1.16", "1.17", "1.18"]
+      - GO_VERSION: ["1.17", "1.18"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
diff --git a/internal/boring/boring.go b/internal/boring/boring.go
--- a/internal/boring/boring.go
+++ b/internal/boring/boring.go
@@ -19,5 +19,5 @@
 		log.Info("FIPS mode is enabled. Using an external SSL library.")
 		return
 	}
-	log.Info("Gitaly was compiled with FIPS mode, but an external SSL library was not enabled.")
+	log.Info("gitlab-shell was compiled with FIPS mode, but an external SSL library was not enabled.")
 }
diff --git a/internal/command/receivepack/gitalycall_test.go b/internal/command/receivepack/gitalycall_test.go
--- a/internal/command/receivepack/gitalycall_test.go
+++ b/internal/command/receivepack/gitalycall_test.go
@@ -57,8 +57,10 @@
 			args.GitlabKeyId = tc.keyId
 		}
 
+		cfg := &config.Config{GitlabUrl: url}
+		cfg.GitalyClient.InitSidechannelRegistry(context.Background())
 		cmd := &Command{
-			Config:     &config.Config{GitlabUrl: url},
+			Config:     cfg,
 			Args:       args,
 			ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 		}
diff --git a/internal/command/uploadarchive/gitalycall_test.go b/internal/command/uploadarchive/gitalycall_test.go
--- a/internal/command/uploadarchive/gitalycall_test.go
+++ b/internal/command/uploadarchive/gitalycall_test.go
@@ -41,8 +41,10 @@
 		Env:         env,
 	}
 
+	cfg := &config.Config{GitlabUrl: url}
+	cfg.GitalyClient.InitSidechannelRegistry(context.Background())
 	cmd := &Command{
-		Config:     &config.Config{GitlabUrl: url},
+		Config:     cfg,
 		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -15,24 +15,7 @@
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
 	gc := handler.NewGitalyCommand(c.Config, string(commandargs.UploadPack), response)
 
-	if response.Gitaly.UseSidechannel {
-		request := &pb.SSHUploadPackWithSidechannelRequest{
-			Repository:       &response.Gitaly.Repo,
-			GitProtocol:      c.Args.Env.GitProtocolVersion,
-			GitConfigOptions: response.GitConfigOptions,
-		}
-
-		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
-			ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
-			defer cancel()
-
-			registry := c.Config.GitalyClient.SidechannelRegistry
-			rw := c.ReadWriter
-			return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
-		})
-	}
-
-	request := &pb.SSHUploadPackRequest{
+	request := &pb.SSHUploadPackWithSidechannelRequest{
 		Repository:       &response.Gitaly.Repo,
 		GitProtocol:      c.Args.Env.GitProtocolVersion,
 		GitConfigOptions: response.GitConfigOptions,
@@ -42,7 +25,8 @@
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
+		registry := c.Config.GitalyClient.SidechannelRegistry
 		rw := c.ReadWriter
-		return client.UploadPack(ctx, conn, rw.In, rw.Out, rw.ErrOut, request)
+		return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
 	})
 }
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -41,62 +41,6 @@
 		Env:         env,
 	}
 
-	cmd := &Command{
-		Config:     &config.Config{GitlabUrl: url},
-		Args:       args,
-		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
-	}
-
-	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
-	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
-
-	err := cmd.Execute(ctx)
-	require.NoError(t, err)
-
-	require.Equal(t, "UploadPack: "+repo, output.String())
-
-	for k, v := range map[string]string{
-		"gitaly-feature-cache_invalidator":        "true",
-		"gitaly-feature-inforef_uploadpack_cache": "false",
-		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-pack",
-		"key_id":                                  "123",
-		"user_id":                                 "1",
-		"remote_ip":                               "127.0.0.1",
-		"key_type":                                "key",
-	} {
-		actual := testServer.ReceivedMD[k]
-		require.Len(t, actual, 1)
-		require.Equal(t, v, actual[0])
-	}
-	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
-	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
-}
-
-func TestUploadPack_withSidechannel(t *testing.T) {
-	gitalyAddress, testServer := testserver.StartGitalyServer(t)
-
-	requests := requesthandlers.BuildAllowedWithGitalyHandlersWithSidechannel(t, gitalyAddress)
-	url := testserver.StartHttpServer(t, requests)
-
-	output := &bytes.Buffer{}
-	input := &bytes.Buffer{}
-
-	userId := "1"
-	repo := "group/repo"
-
-	env := sshenv.Env{
-		IsSSHConnection: true,
-		OriginalCommand: "git-upload-pack " + repo,
-		RemoteAddr:      "127.0.0.1",
-	}
-
-	args := &commandargs.Shell{
-		GitlabKeyId: userId,
-		CommandType: commandargs.UploadPack,
-		SshArgs:     []string{"git-upload-pack", repo},
-		Env:         env,
-	}
-
 	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -32,7 +32,6 @@
 	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
 	WebListen               string   `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
-	LoginGraceTimeSeconds   uint64   `yaml:"login_grace_time"`
 	GracePeriodSeconds      uint64   `yaml:"grace_period"`
 	ReadinessProbe          string   `yaml:"readiness_probe"`
 	LivenessProbe           string   `yaml:"liveness_probe"`
@@ -45,7 +44,6 @@
 	ReadTimeoutSeconds uint64 `yaml:"read_timeout"`
 	CaFile             string `yaml:"ca_file"`
 	CaPath             string `yaml:"ca_path"`
-	SelfSignedCert     bool   `yaml:"self_signed_cert"`
 }
 
 type MercurialConfig struct {
@@ -106,7 +104,6 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
-		LoginGraceTimeSeconds:   60,
 		GracePeriodSeconds:      10,
 		ReadinessProbe:          "/start",
 		LivenessProbe:           "/health",
@@ -118,10 +115,6 @@
 	}
 )
 
-func (sc *ServerConfig) LoginGraceTime() time.Duration {
-	return time.Duration(sc.LoginGraceTimeSeconds) * time.Second
-}
-
 func (sc *ServerConfig) GracePeriod() time.Duration {
 	return time.Duration(sc.GracePeriodSeconds) * time.Second
 }
@@ -139,7 +132,6 @@
 			c.GitlabRelativeURLRoot,
 			c.HttpSettings.CaFile,
 			c.HttpSettings.CaPath,
-			c.HttpSettings.SelfSignedCert,
 			c.HttpSettings.ReadTimeoutSeconds,
 			nil,
 		)
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
--- a/internal/gitaly/gitaly.go
+++ b/internal/gitaly/gitaly.go
@@ -21,10 +21,9 @@
 )
 
 type Command struct {
-	ServiceName     string
-	Address         string
-	Token           string
-	DialSidechannel bool
+	ServiceName string
+	Address     string
+	Token       string
 }
 
 type connectionsCache struct {
@@ -125,9 +124,5 @@
 		)
 	}
 
-	if cmd.DialSidechannel {
-		return client.DialSidechannel(ctx, cmd.Address, c.SidechannelRegistry, connOpts)
-	}
-
-	return client.DialContext(ctx, cmd.Address, connOpts)
+	return client.DialSidechannel(ctx, cmd.Address, c.SidechannelRegistry, connOpts)
 }
diff --git a/internal/gitaly/gitaly_test.go b/internal/gitaly/gitaly_test.go
--- a/internal/gitaly/gitaly_test.go
+++ b/internal/gitaly/gitaly_test.go
@@ -13,7 +13,7 @@
 func TestPrometheusMetrics(t *testing.T) {
 	metrics.GitalyConnectionsTotal.Reset()
 
-	c := &Client{}
+	c := newClient()
 
 	cmd := Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9999"}
 	c.newConnection(context.Background(), cmd)
@@ -31,7 +31,7 @@
 }
 
 func TestCachedConnections(t *testing.T) {
-	c := &Client{}
+	c := newClient()
 
 	require.Len(t, c.cache.connections, 0)
 
@@ -51,3 +51,9 @@
 	require.NoError(t, err)
 	require.Len(t, c.cache.connections, 2)
 }
+
+func newClient() *Client {
+	c := &Client{}
+	c.InitSidechannelRegistry(context.Background())
+	return c
+}
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -32,11 +32,10 @@
 }
 
 type Gitaly struct {
-	Repo           pb.Repository     `json:"repository"`
-	Address        string            `json:"address"`
-	Token          string            `json:"token"`
-	Features       map[string]string `json:"features"`
-	UseSidechannel bool              `json:"use_sidechannel"`
+	Repo     pb.Repository     `json:"repository"`
+	Address  string            `json:"address"`
+	Token    string            `json:"token"`
+	Features map[string]string `json:"features"`
 }
 
 type CustomPayloadData struct {
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -86,41 +86,6 @@
 	}
 }
 
-func TestSidechannelFlag(t *testing.T) {
-	okResponse := testResponse{body: responseBody(t, "allowed_sidechannel.json"), status: http.StatusOK}
-	client := setup(t,
-		map[string]testResponse{"first": okResponse},
-		map[string]testResponse{"1": okResponse},
-	)
-
-	testCases := []struct {
-		desc string
-		args *commandargs.Shell
-		who  string
-	}{
-		{
-			desc: "Provide key id within the request",
-			args: &commandargs.Shell{GitlabKeyId: "1"},
-			who:  "key-1",
-		}, {
-			desc: "Provide username within the request",
-			args: &commandargs.Shell{GitlabUsername: "first"},
-			who:  "user-1",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			result, err := client.Verify(context.Background(), tc.args, uploadPackAction, repo)
-			require.NoError(t, err)
-
-			response := buildExpectedResponse(tc.who)
-			response.Gitaly.UseSidechannel = true
-			require.Equal(t, response, result)
-		})
-	}
-}
-
 func TestGeoPushGetCustomAction(t *testing.T) {
 	client := setup(t, map[string]testResponse{
 		"custom": {
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -33,10 +33,9 @@
 
 func NewGitalyCommand(cfg *config.Config, serviceName string, response *accessverifier.Response) *GitalyCommand {
 	gc := gitaly.Command{
-		ServiceName:     serviceName,
-		Address:         response.Gitaly.Address,
-		Token:           response.Gitaly.Token,
-		DialSidechannel: response.Gitaly.UseSidechannel,
+		ServiceName: serviceName,
+		Address:     response.Gitaly.Address,
+		Token:       response.Gitaly.Token,
 	}
 
 	return &GitalyCommand{Config: cfg, Response: response, Command: gc}
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -29,7 +29,7 @@
 
 func TestRunGitalyCommand(t *testing.T) {
 	cmd := NewGitalyCommand(
-		&config.Config{},
+		newConfig(),
 		string(commandargs.UploadPack),
 		&accessverifier.Response{
 			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
@@ -46,14 +46,12 @@
 
 func TestCachingOfGitalyConnections(t *testing.T) {
 	ctx := context.Background()
-	cfg := &config.Config{}
-	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+	cfg := newConfig()
 	response := &accessverifier.Response{
 		Username: "user",
 		Gitaly: accessverifier.Gitaly{
-			Address:        "tcp://localhost:9999",
-			Token:          "token",
-			UseSidechannel: true,
+			Address: "tcp://localhost:9999",
+			Token:   "token",
 		},
 	}
 
@@ -71,7 +69,7 @@
 }
 
 func TestMissingGitalyAddress(t *testing.T) {
-	cmd := GitalyCommand{Config: &config.Config{}}
+	cmd := GitalyCommand{Config: newConfig()}
 
 	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, nil))
 	require.EqualError(t, err, "no gitaly_address given")
@@ -79,7 +77,7 @@
 
 func TestUnavailableGitalyErr(t *testing.T) {
 	cmd := NewGitalyCommand(
-		&config.Config{},
+		newConfig(),
 		string(commandargs.UploadPack),
 		&accessverifier.Response{
 			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
@@ -101,7 +99,7 @@
 		{
 			name: "gitaly_feature_flags",
 			gc: NewGitalyCommand(
-				&config.Config{},
+				newConfig(),
 				string(commandargs.UploadPack),
 				&accessverifier.Response{
 					Gitaly: accessverifier.Gitaly{
@@ -209,3 +207,9 @@
 		})
 	}
 }
+
+func newConfig() *config.Config {
+	cfg := &config.Config{}
+	cfg.GitalyClient.InitSidechannelRegistry(context.Background())
+	return cfg
+}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -32,7 +32,7 @@
 	Config *config.Config
 
 	status       status
-	statusMu     sync.Mutex
+	statusMu     sync.RWMutex
 	wg           sync.WaitGroup
 	listener     net.Listener
 	serverConfig *serverConfig
@@ -139,8 +139,8 @@
 }
 
 func (s *Server) getStatus() status {
-	s.statusMu.Lock()
-	defer s.statusMu.Unlock()
+	s.statusMu.RLock()
+	defer s.statusMu.RUnlock()
 
 	return s.status
 }
@@ -179,12 +179,11 @@
 
 	ctxlog.Info("server: handleConn: start")
 
-	sconn, chans, reqs, err := s.initSSHConnection(ctx, nconn)
+	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
 		ctxlog.WithError(err).Error("server: handleConn: failed to initialize SSH connection")
 		return
 	}
-
 	go ssh.DiscardRequests(reqs)
 
 	var establishSessionDuration float64
@@ -211,20 +210,6 @@
 	}).Info("server: handleConn: done")
 }
 
-func (s *Server) initSSHConnection(ctx context.Context, nconn net.Conn) (sconn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request, err error) {
-	timer := time.AfterFunc(s.Config.Server.LoginGraceTime(), func() {
-		nconn.Close()
-	})
-	defer func() {
-		// If time.Stop() equals false, that means that AfterFunc has been executed
-		if !timer.Stop() {
-			err = fmt.Errorf("initSSHConnection: ssh handshake timeout of %v", s.Config.Server.LoginGraceTime())
-		}
-	}()
-
-	return ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
-}
-
 func unconditionalRequirePolicy(_ net.Addr) (proxyproto.Policy, error) {
 	return proxyproto.REQUIRE, nil
 }
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -3,7 +3,6 @@
 import (
 	"context"
 	"fmt"
-	"net"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -135,33 +134,6 @@
 	require.Nil(t, s.Shutdown())
 }
 
-func TestLoginGraceTime(t *testing.T) {
-	s := setupServer(t)
-
-	unauthenticatedRequestStatus := make(chan string)
-	completed := make(chan bool)
-
-	clientCfg := clientConfig(t)
-	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
-		unauthenticatedRequestStatus <- "authentication-started"
-		<-completed // Wait infinitely
-
-		return nil
-	}
-
-	go func() {
-		// Start an SSH connection that never ends
-		ssh.Dial("tcp", serverUrl, clientCfg)
-	}()
-
-	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
-
-	// Verify that the server shutdowns instantly without waiting for any connection to execute
-	// That means that the server doesn't have any connections to wait for
-	require.NoError(t, s.Shutdown())
-	verifyStatus(t, s, StatusClosed)
-}
-
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
@@ -211,7 +183,6 @@
 	cfg.User = user
 	cfg.Server.Listen = serverUrl
 	cfg.Server.ConcurrentSessionsLimit = 1
-	cfg.Server.LoginGraceTimeSeconds = 1
 	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
 
 	s, err := NewServer(cfg)
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -81,14 +81,6 @@
 }
 
 func BuildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
-	return buildAllowedWithGitalyHandlers(t, gitalyAddress, false)
-}
-
-func BuildAllowedWithGitalyHandlersWithSidechannel(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
-	return buildAllowedWithGitalyHandlers(t, gitalyAddress, true)
-}
-
-func buildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string, useSidechannel bool) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/allowed",
@@ -116,9 +108,6 @@
 						},
 					},
 				}
-				if useSidechannel {
-					body["gitaly"].(map[string]interface{})["use_sidechannel"] = true
-				}
 				require.NoError(t, json.NewEncoder(w).Encode(body))
 			},
 		},
diff --git a/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json b/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
deleted file mode 100644
--- a/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-	"status": true,
-	"gl_repository": "project-26",
-	"gl_project_path": "group/private",
-	"gl_id": "user-1",
-	"gl_username": "root",
-	"git_config_options": ["option"],
-	"gitaly": {
-		"repository": {
-			"storage_name": "default",
-			"relative_path": "@hashed/5f/9c/5f9c4ab08cac7457e9111a30e4664920607ea2c115a1433d7be98e97e64244ca.git",
-			"git_object_directory": "path/to/git_object_directory",
-			"git_alternate_object_directories": ["path/to/git_alternate_object_directory"],
-			"gl_repository": "project-26",
-			"gl_project_path": "group/private"
-		},
-		"address": "unix:gitaly.socket",
-		"token": "token",
-               "use_sidechannel": true
-	},
-	"git_protocol": "protocol",
-	"gl_console_messages": ["console", "message"]
-}
# HG changeset patch
# User Georges Racinet on purity.racinet.fr <georges@racinet.fr>
# Date 1663175854 -7200
#      Wed Sep 14 19:17:34 2022 +0200
# Branch heptapod
# Node ID df16c7bb5675b839c8b7cac2252379e32850a7ab
# Parent  2daff2db282c6fbf244ff5d8e2b386bc8253f60d
build: restored build time information

This is displayed by `gitlab-shell -version`

Was inadvertently removed in 500ed5c3c7f5 (no mention of it
in changeset description)

diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -13,6 +13,8 @@
 ## 14.0.0
 
 - Jump to upstream GitLab Shell v14.0.0 (pre-GitLab 15.0)
+- `gitlab-shell -version` now displays the build time as expected
+  instead of an empty string, giving a trailing dash (e.g., `hpd-13-25-`).
 
 ## 13.25.0
 
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,7 @@
 FIPS_MODE ?= 0
 GO_SOURCES := $(shell find . -name '*.go')
 VERSION_STRING := $(shell ./compute_version || echo unknown)
+BUILD_TIME := $(shell date -u +%Y%m%d.%H%M%S)
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
 
 ifeq (${FIPS_MODE}, 1)
# HG changeset patch
# User Georges Racinet on purity.racinet.fr <georges@racinet.fr>
# Date 1663176041 -7200
#      Wed Sep 14 19:20:41 2022 +0200
# Branch heptapod
# Node ID 0f10343a01bb4a060a4ff3f8531d81dbeffe67a9
# Parent  df16c7bb5675b839c8b7cac2252379e32850a7ab
Added tag heptapod-14.0.0 for changeset df16c7bb5675

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -40,3 +40,4 @@
 61814961ec7c871e9a081f4399536e5b8a62fabf heptapod-13.23.0
 eaa400f46c97a398037f0a72a5a30836f31a3586 heptapod-13.24.0
 bbf4eff5db1a60372d6a9828b15464d5207c1fbf heptapod-13.25.0
+df16c7bb5675b839c8b7cac2252379e32850a7ab heptapod-14.0.0
# HG changeset patch
# User Georges Racinet on purity.racinet.fr <georges@racinet.fr>
# Date 1663179966 -7200
#      Wed Sep 14 20:26:06 2022 +0200
# Branch heptapod
# Node ID 98d781e90b1bb472b9c37ad5fbdbed541e1457c5
# Parent  0f10343a01bb4a060a4ff3f8531d81dbeffe67a9
# Parent  95f105751282580f695ff4bec8577de261720bbd
Merged upstream v14.2.0 into Heptapod Shell

diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -1,4 +1,4 @@
-* @ashmckenzie @igor.drozdov @nick.thomas @patrickbajao
+* @ashmckenzie @igor.drozdov @patrickbajao
 
 [Documentation]
 *.md @aqualls
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,4 +1,21 @@
+v14.2.0
+
+- Implement ClientKeepAlive option !622
+- build: bump go-proxyproto to 0.6.2 !610
+
+v14.1.1
+
+- Log the error that happens on sconn.Wait() !613
+
+v14.1.0
+
+- Make PROXY policy configurable !619
+- Exclude authentication errors from apdex !611
+- Fix check_ip argument when gitlab-sshd used with PROXY protocol !616
+- Use labkit for FIPS check !607
+
 v14.0.0
+
 - Always use Gitaly sidechannel connections !567
 
 v13.26.0
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 14.2.0
+
+- Jump to upstream GitLab Shell v14.2.0 (pre-GitLab 15.0)
+
 ## 14.0.0
 
 - Jump to upstream GitLab Shell v14.0.0 (pre-GitLab 15.0)
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,11 @@
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
 
 ifeq (${FIPS_MODE}, 1)
-    BUILD_TAGS += boringcrypto
+    # boringcrypto tag is added automatically by golang-fips compiler
+    BUILD_TAGS += fips
+    # If the golang-fips compiler is built with CGO_ENABLED=0, this needs to be
+    # explicitly switched on.
+    export CGO_ENABLED=1
 endif
 
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)" -mod=mod
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.0.0
+14.2.0
diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -196,8 +196,8 @@
 		require.NoError(t, err)
 		require.True(t, token.Valid)
 		require.Equal(t, "gitlab-shell", claims.Issuer)
-		require.Equal(t, time.Now().Truncate(time.Second), claims.IssuedAt.Time, time.Second)
-		require.Equal(t, time.Now().Truncate(time.Second).Add(time.Minute), claims.ExpiresAt.Time, time.Second)
+		require.WithinDuration(t, time.Now().Truncate(time.Second), claims.IssuedAt.Time, time.Second)
+		require.WithinDuration(t, time.Now().Truncate(time.Second).Add(time.Minute), claims.ExpiresAt.Time, time.Second)
 	}
 
 	t.Run("JWT authentication headers for GET", func(t *testing.T) {
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -8,10 +8,10 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
+	"gitlab.com/gitlab-org/labkit/fips"
 	"gitlab.com/gitlab-org/labkit/log"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/boring"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -74,7 +74,7 @@
 	cmdName := reflect.TypeOf(cmd).String()
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("gitlab-shell: main: executing command")
-	boring.CheckBoring()
+	fips.Check()
 
 	if err := cmd.Execute(ctx); err != nil {
 		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -85,10 +85,15 @@
   # Set to true if gitlab-sshd is being fronted by a load balancer that implements
   # the PROXY protocol.
   proxy_protocol: false
+  # Proxy protocol policy ("use", "require", "reject", "ignore"), "use" is the default value
+  # Values: https://github.com/pires/go-proxyproto/blob/195fedcfbfc1be163f3a0d507fac1709e9d81fed/policy.go#L20
+  proxy_policy: "use"
   # Address which the server listens on HTTP for monitoring/health checks. Defaults to localhost:9122.
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
+  # Sets an interval after which server will send keepalive message to a client
+  client_alive_interval: 15
   # The server waits for this time (in seconds) for the ongoing connections to complete before shutting down. Defaults to 10.
   grace_period: 10
   # The endpoint that returns 200 OK if the server is ready to receive incoming connections; otherwise, it returns 503 Service Unavailable. Defaults to "/start".
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -9,12 +9,12 @@
 	github.com/mattn/go-shellwords v1.0.11
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
-	github.com/pires/go-proxyproto v0.6.1
-	github.com/prometheus/client_golang v1.11.0
+	github.com/pires/go-proxyproto v0.6.2
+	github.com/prometheus/client_golang v1.12.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
-	gitlab.com/gitlab-org/labkit v1.12.0
-	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
+	gitlab.com/gitlab-org/labkit v1.14.0
+	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/grpc v1.40.0
 	gopkg.in/yaml.v2 v2.4.0
@@ -33,7 +33,7 @@
 	github.com/beevik/ntp v0.3.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
-	github.com/cespare/xxhash/v2 v2.1.1 // indirect
+	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/client9/reopen v1.0.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/go-ole/go-ole v1.2.4 // indirect
@@ -55,8 +55,8 @@
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/prometheus/client_model v0.2.0 // indirect
-	github.com/prometheus/common v0.26.0 // indirect
-	github.com/prometheus/procfs v0.6.0 // indirect
+	github.com/prometheus/common v0.32.1 // indirect
+	github.com/prometheus/procfs v0.7.3 // indirect
 	github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect
 	github.com/shirou/gopsutil/v3 v3.21.2 // indirect
 	github.com/sirupsen/logrus v1.8.1 // indirect
@@ -69,8 +69,8 @@
 	go.uber.org/atomic v1.7.0 // indirect
 	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
 	golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a // indirect
-	golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 // indirect
-	golang.org/x/text v0.3.6 // indirect
+	golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
+	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	google.golang.org/api v0.54.0 // indirect
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -165,8 +165,9 @@
 github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -255,12 +256,13 @@
 github.com/getsentry/sentry-go v0.5.1/go.mod h1:B8H7x8TYDPkeWPRzGpIiFO97LZP6rL8A3hEt8lUItMw=
 github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
 github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
-github.com/getsentry/sentry-go v0.11.0/go.mod h1:KBQIxiZAetw62Cj8Ri964vAEWVdgfaUCn30Q3bCvANo=
+github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
 github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
+github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
 github.com/git-lfs/git-lfs v1.5.1-0.20210304194248-2e1d981afbe3/go.mod h1:8Xqs4mqL7o6xEnaXckIgELARTeK7RYtm3pBab7S79Js=
 github.com/git-lfs/gitobj/v2 v2.0.1/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
 github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
@@ -294,6 +296,7 @@
 github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
+github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@@ -316,6 +319,8 @@
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
+github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
 github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
 github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
@@ -531,6 +536,7 @@
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jteeuwen/go-bindata v3.0.8-0.20180305030458-6025e8de665b+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
@@ -577,6 +583,7 @@
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
+github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -603,6 +610,8 @@
 github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -611,6 +620,7 @@
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-shellwords v0.0.0-20190425161501-2444a32a19f4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
@@ -641,6 +651,7 @@
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/montanaflynn/stats v0.0.0-20151014174947-eeaced052adb/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -706,8 +717,8 @@
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
 github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
-github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
-github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
+github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -723,8 +734,9 @@
 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
-github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
+github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -737,15 +749,17 @@
 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
-github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
+github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
+github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
@@ -846,6 +860,7 @@
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
+github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
 github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
 github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
@@ -880,8 +895,8 @@
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
 gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
 gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
-gitlab.com/gitlab-org/labkit v1.12.0 h1:XVwGqXfHrhEr2bzZbYy/eA7xulAeyvk9HB3Q7nH2H4I=
-gitlab.com/gitlab-org/labkit v1.12.0/go.mod h1:tnvyKiPF/Y0MeBy+ACYWRWexOEQk6PTRGUc9GstcnXc=
+gitlab.com/gitlab-org/labkit v1.14.0 h1:LSrvHgybidPyH8fHnsy1GBghrLR4kFObFrtZwUfCgAI=
+gitlab.com/gitlab-org/labkit v1.14.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
@@ -1010,6 +1025,8 @@
 golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -1111,6 +1128,7 @@
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1125,8 +1143,11 @@
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1136,12 +1157,14 @@
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/internal/boring/boring.go b/internal/boring/boring.go
deleted file mode 100644
--- a/internal/boring/boring.go
+++ /dev/null
@@ -1,23 +0,0 @@
-//go:build boringcrypto
-// +build boringcrypto
-
-package boring
-
-import (
-	"crypto/boring"
-
-	"gitlab.com/gitlab-org/labkit/log"
-)
-
-// CheckBoring checks whether FIPS crypto has been enabled. For the FIPS Go
-// compiler in https://github.com/golang-fips/go, this requires that:
-//
-// 1. The kernel has FIPS enabled (e.g. `/proc/sys/crypto/fips_enabled` is 1).
-// 2. A system OpenSSL can be dynamically loaded via ldopen().
-func CheckBoring() {
-	if boring.Enabled() {
-		log.Info("FIPS mode is enabled. Using an external SSL library.")
-		return
-	}
-	log.Info("gitlab-shell was compiled with FIPS mode, but an external SSL library was not enabled.")
-}
diff --git a/internal/boring/notboring.go b/internal/boring/notboring.go
deleted file mode 100644
--- a/internal/boring/notboring.go
+++ /dev/null
@@ -1,9 +0,0 @@
-//go:build !boringcrypto
-// +build !boringcrypto
-
-package boring
-
-// CheckBoring does nothing when the boringcrypto tag is not in the
-// build.
-func CheckBoring() {
-}
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -28,14 +28,16 @@
 )
 
 type ServerConfig struct {
-	Listen                  string   `yaml:"listen,omitempty"`
-	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
-	WebListen               string   `yaml:"web_listen,omitempty"`
-	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
-	GracePeriodSeconds      uint64   `yaml:"grace_period"`
-	ReadinessProbe          string   `yaml:"readiness_probe"`
-	LivenessProbe           string   `yaml:"liveness_probe"`
-	HostKeyFiles            []string `yaml:"host_key_files,omitempty"`
+	Listen                     string   `yaml:"listen,omitempty"`
+	ProxyProtocol              bool     `yaml:"proxy_protocol,omitempty"`
+	ProxyPolicy                string   `yaml:"proxy_policy,omitempty"`
+	WebListen                  string   `yaml:"web_listen,omitempty"`
+	ConcurrentSessionsLimit    int64    `yaml:"concurrent_sessions_limit,omitempty"`
+	ClientAliveIntervalSeconds int64    `yaml:"client_alive_interval,omitempty"`
+	GracePeriodSeconds         uint64   `yaml:"grace_period"`
+	ReadinessProbe             string   `yaml:"readiness_probe"`
+	LivenessProbe              string   `yaml:"liveness_probe"`
+	HostKeyFiles               []string `yaml:"host_key_files,omitempty"`
 }
 
 type HttpSettingsConfig struct {
@@ -101,12 +103,13 @@
 	}
 
 	DefaultServerConfig = ServerConfig{
-		Listen:                  "[::]:22",
-		WebListen:               "localhost:9122",
-		ConcurrentSessionsLimit: 10,
-		GracePeriodSeconds:      10,
-		ReadinessProbe:          "/start",
-		LivenessProbe:           "/health",
+		Listen:                     "[::]:22",
+		WebListen:                  "localhost:9122",
+		ConcurrentSessionsLimit:    10,
+		GracePeriodSeconds:         10,
+		ClientAliveIntervalSeconds: 15,
+		ReadinessProbe:             "/start",
+		LivenessProbe:              "/health",
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
@@ -115,6 +118,10 @@
 	}
 )
 
+func (sc *ServerConfig) ClientAliveInterval() time.Duration {
+	return time.Duration(sc.ClientAliveIntervalSeconds) * time.Second
+}
+
 func (sc *ServerConfig) GracePeriod() time.Duration {
 	return time.Duration(sc.GracePeriodSeconds) * time.Second
 }
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -3,6 +3,7 @@
 import (
 	"context"
 	"fmt"
+	"net"
 	"net/http"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
@@ -85,7 +86,7 @@
 		request.KeyId = args.GitlabKeyId
 	}
 
-	request.CheckIp = args.Env.RemoteAddr
+	request.CheckIp = parseIP(args.Env.RemoteAddr)
 
 	response, err := c.client.Post(ctx, "/allowed", request)
 	if err != nil {
@@ -116,3 +117,18 @@
 func (r *Response) IsCustomAction() bool {
 	return r.StatusCode == http.StatusMultipleChoices
 }
+
+func parseIP(remoteAddr string) string {
+	// The remoteAddr field can be filled by:
+	// 1. An IP address via the SSH_CONNECTION environment variable
+	// 2. A host:port combination via the PROXY protocol
+	ip, _, err := net.SplitHostPort(remoteAddr)
+
+	// If we don't have a port or can't parse this address for some reason,
+	// just return the original string.
+	if err != nil {
+		return remoteAddr
+	}
+
+	return ip
+}
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -14,6 +14,7 @@
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
 
@@ -180,6 +181,51 @@
 	}
 }
 
+func TestCheckIP(t *testing.T) {
+	testCases := []struct {
+		desc            string
+		remoteAddr      string
+		expectedCheckIp string
+	}{
+		{
+			desc:            "IPv4 address",
+			remoteAddr:      "18.245.0.42",
+			expectedCheckIp: "18.245.0.42",
+		},
+		{
+			desc:            "IPv6 address",
+			remoteAddr:      "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+			expectedCheckIp: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+		},
+		{
+			desc:            "Host and port",
+			remoteAddr:      "18.245.0.42:6345",
+			expectedCheckIp: "18.245.0.42",
+		},
+		{
+			desc:            "IPv6 host and port",
+			remoteAddr:      "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80",
+			expectedCheckIp: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+		},
+		{
+			desc:            "Bad remote addr",
+			remoteAddr:      "[127.0",
+			expectedCheckIp: "[127.0",
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			client := setupWithApiInspector(t,
+				func(r *Request) {
+					require.Equal(t, tc.expectedCheckIp, r.CheckIp)
+				})
+
+			sshEnv := sshenv.Env{RemoteAddr: tc.remoteAddr}
+			client.Verify(context.Background(), &commandargs.Shell{Env: sshEnv}, uploadPackAction, repo)
+		})
+	}
+}
+
 type testResponse struct {
 	body   []byte
 	status int
@@ -225,3 +271,29 @@
 
 	return client
 }
+
+func setupWithApiInspector(t *testing.T, inspector func(*Request)) *Client {
+	t.Helper()
+	requests := []testserver.TestRequestHandler{
+		{
+			Path: "/api/v4/internal/allowed",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				b, err := io.ReadAll(r.Body)
+				require.NoError(t, err)
+
+				var requestBody *Request
+				err = json.Unmarshal(b, &requestBody)
+				require.NoError(t, err)
+
+				inspector(requestBody)
+			},
+		},
+	}
+
+	url := testserver.StartSocketHttpServer(t, requests)
+
+	client, err := NewClient(&config.Config{GitlabUrl: url})
+	require.NoError(t, err)
+
+	return client
+}
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -2,32 +2,46 @@
 
 import (
 	"context"
+	"time"
 
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
 
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
+const KeepAliveMsg = "keepalive@openssh.com"
+
 type connection struct {
+	cfg                *config.Config
 	concurrentSessions *semaphore.Weighted
 	remoteAddr         string
+	sconn              *ssh.ServerConn
 }
 
 type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request)
 
-func newConnection(maxSessions int64, remoteAddr string) *connection {
+func newConnection(cfg *config.Config, remoteAddr string, sconn *ssh.ServerConn) *connection {
 	return &connection{
-		concurrentSessions: semaphore.NewWeighted(maxSessions),
+		cfg:                cfg,
+		concurrentSessions: semaphore.NewWeighted(cfg.Server.ConcurrentSessionsLimit),
 		remoteAddr:         remoteAddr,
+		sconn:              sconn,
 	}
 }
 
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
+	if c.cfg.Server.ClientAliveIntervalSeconds > 0 {
+		ticker := time.NewTicker(c.cfg.Server.ClientAliveInterval())
+		defer ticker.Stop()
+		go c.sendKeepAliveMsg(ctx, ticker)
+	}
+
 	for newChannel := range chans {
 		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
@@ -49,6 +63,10 @@
 		}
 
 		go func() {
+			defer func(started time.Time) {
+				metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
+			}(time.Now())
+
 			defer c.concurrentSessions.Release(1)
 
 			// Prevent a panic in a single session from taking out the whole server
@@ -63,3 +81,18 @@
 		}()
 	}
 }
+
+func (c *connection) sendKeepAliveMsg(ctx context.Context, ticker *time.Ticker) {
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-ticker.C:
+			ctxlog.Debug("session: handleShell: send keepalive message to a client")
+
+			c.sconn.SendRequest(KeepAliveMsg, true, nil)
+		}
+	}
+}
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -3,10 +3,14 @@
 import (
 	"context"
 	"errors"
+	"sync"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 )
 
 type rejectCall struct {
@@ -47,8 +51,32 @@
 	return f.extraData
 }
 
+type fakeConn struct {
+	ssh.Conn
+
+	sentRequestName string
+	mu              sync.Mutex
+}
+
+func (f *fakeConn) SentRequestName() string {
+	f.mu.Lock()
+	defer f.mu.Unlock()
+
+	return f.sentRequestName
+}
+
+func (f *fakeConn) SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error) {
+	f.mu.Lock()
+	defer f.mu.Unlock()
+
+	f.sentRequestName = name
+
+	return true, nil, nil
+}
+
 func setup(sessionsNum int64, newChannel *fakeNewChannel) (*connection, chan ssh.NewChannel) {
-	conn := newConnection(sessionsNum, "127.0.0.1:50000")
+	cfg := &config.Config{Server: config.ServerConfig{ConcurrentSessionsLimit: sessionsNum, ClientAliveIntervalSeconds: 1}}
+	conn := newConnection(cfg, "127.0.0.1:50000", &ssh.ServerConn{&fakeConn{}, nil})
 
 	chans := make(chan ssh.NewChannel, 1)
 	chans <- newChannel
@@ -145,3 +173,16 @@
 
 	require.False(t, channelHandled)
 }
+
+func TestClientAliveInterval(t *testing.T) {
+	f := &fakeConn{}
+
+	conn := newConnection(&config.Config{}, "127.0.0.1:50000", &ssh.ServerConn{f, nil})
+
+	ticker := time.NewTicker(time.Millisecond)
+	defer ticker.Stop()
+
+	go conn.sendKeepAliveMsg(context.Background(), ticker)
+
+	require.Eventually(t, func() bool { return KeepAliveMsg == f.SentRequestName() }, time.Second, time.Millisecond)
+}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -5,6 +5,7 @@
 	"fmt"
 	"net"
 	"net/http"
+	"strings"
 	"sync"
 	"time"
 
@@ -95,7 +96,7 @@
 	if s.Config.Server.ProxyProtocol {
 		sshListener = &proxyproto.Listener{
 			Listener:          sshListener,
-			Policy:            unconditionalRequirePolicy,
+			Policy:            s.requirePolicy,
 			ReadHeaderTimeout: ProxyHeaderTimeout,
 		}
 
@@ -146,19 +147,8 @@
 }
 
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
-	success := false
-
 	metrics.SshdConnectionsInFlight.Inc()
-	started := time.Now()
-	defer func() {
-		metrics.SshdConnectionsInFlight.Dec()
-		metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
-
-		metrics.SliSshdSessionsTotal.Inc()
-		if !success {
-			metrics.SliSshdSessionsErrorsTotal.Inc()
-		}
-	}()
+	defer metrics.SshdConnectionsInFlight.Dec()
 
 	remoteAddr := nconn.RemoteAddr().String()
 
@@ -174,6 +164,8 @@
 	defer func() {
 		if err := recover(); err != nil {
 			ctxlog.Warn("panic handling session")
+
+			metrics.SliSshdSessionsErrorsTotal.Inc()
 		}
 	}()
 
@@ -181,13 +173,14 @@
 
 	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
-		ctxlog.WithError(err).Error("server: handleConn: failed to initialize SSH connection")
+		ctxlog.WithError(err).Warn("server: handleConn: failed to initialize SSH connection")
 		return
 	}
 	go ssh.DiscardRequests(reqs)
 
+	started := time.Now()
 	var establishSessionDuration float64
-	conn := newConnection(s.Config.Server.ConcurrentSessionsLimit, remoteAddr)
+	conn := newConnection(s.Config, remoteAddr, sconn)
 	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
 		establishSessionDuration = time.Since(started).Seconds()
 		metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
@@ -199,17 +192,32 @@
 			remoteAddr:  remoteAddr,
 		}
 
+		metrics.SliSshdSessionsTotal.Inc()
 		session.handle(ctx, requests)
-
-		success = session.success
+		if !session.success {
+			metrics.SliSshdSessionsErrorsTotal.Inc()
+		}
 	})
 
+	reason := sconn.Wait()
 	ctxlog.WithFields(log.Fields{
 		"duration_s":                   time.Since(started).Seconds(),
 		"establish_session_duration_s": establishSessionDuration,
+		"reason":                       reason,
 	}).Info("server: handleConn: done")
 }
 
-func unconditionalRequirePolicy(_ net.Addr) (proxyproto.Policy, error) {
-	return proxyproto.REQUIRE, nil
+func (s *Server) requirePolicy(_ net.Addr) (proxyproto.Policy, error) {
+	// Set the Policy value based on config
+	// Values are taken from https://github.com/pires/go-proxyproto/blob/195fedcfbfc1be163f3a0d507fac1709e9d81fed/policy.go#L20
+	switch strings.ToLower(s.Config.Server.ProxyPolicy) {
+	case "require":
+		return proxyproto.REQUIRE, nil
+	case "ignore":
+		return proxyproto.IGNORE, nil
+	case "reject":
+		return proxyproto.REJECT, nil
+	default:
+		return proxyproto.USE, nil
+	}
 }
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -3,6 +3,7 @@
 import (
 	"context"
 	"fmt"
+	"net"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -10,6 +11,7 @@
 	"testing"
 	"time"
 
+	"github.com/pires/go-proxyproto"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
@@ -48,15 +50,101 @@
 }
 
 func TestListenAndServeRejectsPlainConnectionsWhenProxyProtocolEnabled(t *testing.T) {
-	setupServerWithProxyProtocolEnabled(t)
+	target, err := net.ResolveTCPAddr("tcp", serverUrl)
+	require.NoError(t, err)
 
-	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
-	if client != nil {
-		client.Close()
+	header := &proxyproto.Header{
+		Version:           2,
+		Command:           proxyproto.PROXY,
+		TransportProtocol: proxyproto.TCPv4,
+		SourceAddr: &net.TCPAddr{
+			IP:   net.ParseIP("10.1.1.1"),
+			Port: 1000,
+		},
+		DestinationAddr: target,
 	}
 
-	require.Error(t, err, "Expected plain SSH request to be failed")
-	require.Regexp(t, "ssh: handshake failed", err.Error())
+	testCases := []struct {
+		desc        string
+		proxyPolicy string
+		header      *proxyproto.Header
+		isRejected  bool
+	}{
+		{
+			desc:        "USE (default) without a header",
+			proxyPolicy: "",
+			header:      nil,
+			isRejected:  false,
+		},
+		{
+			desc:        "USE (default) with a header",
+			proxyPolicy: "",
+			header:      header,
+			isRejected:  false,
+		},
+		{
+			desc:        "REQUIRE without a header",
+			proxyPolicy: "require",
+			header:      nil,
+			isRejected:  true,
+		},
+		{
+			desc:        "REQUIRE with a header",
+			proxyPolicy: "require",
+			header:      header,
+			isRejected:  false,
+		},
+		{
+			desc:        "REJECT without a header",
+			proxyPolicy: "reject",
+			header:      nil,
+			isRejected:  false,
+		},
+		{
+			desc:        "REJECT with a header",
+			proxyPolicy: "reject",
+			header:      header,
+			isRejected:  true,
+		},
+		{
+			desc:        "IGNORE without a header",
+			proxyPolicy: "ignore",
+			header:      nil,
+			isRejected:  false,
+		},
+		{
+			desc:        "IGNORE with a header",
+			proxyPolicy: "ignore",
+			header:      header,
+			isRejected:  false,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			setupServerWithConfig(t, &config.Config{Server: config.ServerConfig{ProxyProtocol: true, ProxyPolicy: tc.proxyPolicy}})
+
+			conn, err := net.DialTCP("tcp", nil, target)
+			require.NoError(t, err)
+
+			if tc.header != nil {
+				_, err := header.WriteTo(conn)
+				require.NoError(t, err)
+			}
+
+			sshConn, _, _, err := ssh.NewClientConn(conn, serverUrl, clientConfig(t))
+			if sshConn != nil {
+				sshConn.Close()
+			}
+
+			if tc.isRejected {
+				require.Error(t, err, "Expected plain SSH request to be failed")
+				require.Regexp(t, "ssh: handshake failed", err.Error())
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
 }
 
 func TestCorrelationId(t *testing.T) {
@@ -140,12 +228,6 @@
 	return setupServerWithConfig(t, nil)
 }
 
-func setupServerWithProxyProtocolEnabled(t *testing.T) *Server {
-	t.Helper()
-
-	return setupServerWithConfig(t, &config.Config{Server: config.ServerConfig{ProxyProtocol: true}})
-}
-
 func setupServerWithConfig(t *testing.T, cfg *config.Config) *Server {
 	t.Helper()
 
@@ -183,6 +265,7 @@
 	cfg.User = user
 	cfg.Server.Listen = serverUrl
 	cfg.Server.ConcurrentSessionsLimit = 1
+	cfg.Server.ClientAliveIntervalSeconds = 15
 	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
 
 	s, err := NewServer(cfg)
# HG changeset patch
# User Georges Racinet on purity.racinet.fr <georges@racinet.fr>
# Date 1663180178 -7200
#      Wed Sep 14 20:29:38 2022 +0200
# Branch heptapod
# Node ID 60be5fae5a4266a3ed4eb3508b4731b1f821d8cf
# Parent  98d781e90b1bb472b9c37ad5fbdbed541e1457c5
Added tag heptapod-14.2.0 for changeset 98d781e90b1b

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -41,3 +41,4 @@
 eaa400f46c97a398037f0a72a5a30836f31a3586 heptapod-13.24.0
 bbf4eff5db1a60372d6a9828b15464d5207c1fbf heptapod-13.25.0
 df16c7bb5675b839c8b7cac2252379e32850a7ab heptapod-14.0.0
+98d781e90b1bb472b9c37ad5fbdbed541e1457c5 heptapod-14.2.0
# HG changeset patch
# User Georges Racinet on purity.racinet.fr <georges@racinet.fr>
# Date 1663180324 -7200
#      Wed Sep 14 20:32:04 2022 +0200
# Branch heptapod
# Node ID 7e62e4be19def9c1f1773e326480a9429210bb8d
# Parent  60be5fae5a4266a3ed4eb3508b4731b1f821d8cf
# Parent  c7cb78872f0da7b8083d01a00da0f1c478ee42e3
Merged upstream v14.3.0 into Heptapod Shell

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -92,9 +92,6 @@
 gemnasium-dependency_scanning:
   rules: *workflow_rules
 
-bundler-audit-dependency_scanning:
-  rules: *workflow_rules
-
 # Secret Detection
 secret_detection:
   rules: *workflow_rules
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v14.3.0
+
+- Remove deprecated bundler-audit !626
+- Wait until all Gitaly sessions are executed !624
+
 v14.2.0
 
 - Implement ClientKeepAlive option !622
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 14.3.0
+
+- Bump to upstream GitLab Shell v14.3.0 (GitLab 15.0 series)
+
 ## 14.2.0
 
 - Jump to upstream GitLab Shell v14.2.0 (pre-GitLab 15.0)
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.2.0
+14.3.0
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -39,7 +39,7 @@
 	require.NoError(t, err)
 
 	var actualNames []string
-	for _, m := range ms[0:9] {
+	for _, m := range ms[0:10] {
 		actualNames = append(actualNames, m.GetName())
 	}
 
@@ -47,6 +47,7 @@
 		"gitlab_shell_http_in_flight_requests",
 		"gitlab_shell_http_request_duration_seconds",
 		"gitlab_shell_http_requests_total",
+		"gitlab_shell_sshd_canceled_sessions",
 		"gitlab_shell_sshd_concurrent_limited_sessions_total",
 		"gitlab_shell_sshd_in_flight_connections",
 		"gitlab_shell_sshd_session_duration_seconds",
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -22,6 +22,7 @@
 	sshdHitMaxSessionsName                    = "concurrent_limited_sessions_total"
 	sshdSessionDurationSecondsName            = "session_duration_seconds"
 	sshdSessionEstablishedDurationSecondsName = "session_established_duration_seconds"
+	sshdCanceledSessionsName                  = "canceled_sessions"
 
 	sliSshdSessionsTotalName       = "gitlab_sli:shell_sshd_sessions:total"
 	sliSshdSessionsErrorsTotalName = "gitlab_sli:shell_sshd_sessions:errors_total"
@@ -76,6 +77,15 @@
 		},
 	)
 
+	SshdCanceledSessions = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      sshdCanceledSessionsName,
+			Help:      "The number of canceled gitlab-sshd sessions.",
+		},
+	)
+
 	SliSshdSessionsTotal = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Name: sliSshdSessionsTotalName,
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -6,6 +6,8 @@
 
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
@@ -15,19 +17,25 @@
 
 const KeepAliveMsg = "keepalive@openssh.com"
 
+var EOFTimeout = 10 * time.Second
+
 type connection struct {
 	cfg                *config.Config
 	concurrentSessions *semaphore.Weighted
 	remoteAddr         string
 	sconn              *ssh.ServerConn
+	maxSessions        int64
 }
 
-type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request)
+type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request) error
 
 func newConnection(cfg *config.Config, remoteAddr string, sconn *ssh.ServerConn) *connection {
+	maxSessions := cfg.Server.ConcurrentSessionsLimit
+
 	return &connection{
 		cfg:                cfg,
-		concurrentSessions: semaphore.NewWeighted(cfg.Server.ConcurrentSessionsLimit),
+		maxSessions:        maxSessions,
+		concurrentSessions: semaphore.NewWeighted(maxSessions),
 		remoteAddr:         remoteAddr,
 		sconn:              sconn,
 	}
@@ -76,10 +84,27 @@
 				}
 			}()
 
-			handler(ctx, channel, requests)
+			metrics.SliSshdSessionsTotal.Inc()
+			err := handler(ctx, channel, requests)
+			if err != nil {
+				if grpcstatus.Convert(err).Code() == grpccodes.Canceled {
+					metrics.SshdCanceledSessions.Inc()
+				} else {
+					metrics.SliSshdSessionsErrorsTotal.Inc()
+				}
+			}
+
 			ctxlog.Info("connection: handle: done")
 		}()
 	}
+
+	// When a connection has been prematurely closed we block execution until all concurrent sessions are released
+	// in order to allow Gitaly complete the operations and close all the channels gracefully.
+	// If it didn't happen within timeout, we unblock the execution
+	// Related issue: https://gitlab.com/gitlab-org/gitlab-shell/-/issues/563
+	ctx, cancel := context.WithTimeout(ctx, EOFTimeout)
+	defer cancel()
+	c.concurrentSessions.Acquire(ctx, c.maxSessions)
 }
 
 func (c *connection) sendKeepAliveMsg(ctx context.Context, ticker *time.Ticker) {
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -7,10 +7,14 @@
 	"testing"
 	"time"
 
+	"github.com/prometheus/client_golang/prometheus/testutil"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
 
 type rejectCall struct {
@@ -90,7 +94,7 @@
 
 	numSessions := 0
 	require.NotPanics(t, func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 			numSessions += 1
 			close(chans)
 			panic("This is a panic")
@@ -128,8 +132,9 @@
 	defer cancel()
 
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 			<-ctx.Done() // Keep the accepted channel open until the end of the test
+			return nil
 		})
 	}()
 
@@ -142,9 +147,10 @@
 	conn, chans := setup(1, newChannel)
 
 	channelHandled := false
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 		channelHandled = true
 		close(chans)
+		return nil
 	})
 
 	require.True(t, channelHandled)
@@ -160,8 +166,9 @@
 
 	channelHandled := false
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 			channelHandled = true
+			return nil
 		})
 	}()
 
@@ -186,3 +193,33 @@
 
 	require.Eventually(t, func() bool { return KeepAliveMsg == f.SentRequestName() }, time.Second, time.Millisecond)
 }
+
+func TestSessionsMetrics(t *testing.T) {
+	// Unfortunately, there is no working way to reset Counter (not CounterVec)
+	// https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#pkg-index
+	initialSessionsTotal := testutil.ToFloat64(metrics.SliSshdSessionsTotal)
+	initialSessionsErrorTotal := testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal)
+	initialCanceledSessions := testutil.ToFloat64(metrics.SshdCanceledSessions)
+
+	newChannel := &fakeNewChannel{channelType: "session"}
+
+	conn, chans := setup(1, newChannel)
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		close(chans)
+		return errors.New("custom error")
+	})
+
+	require.InDelta(t, initialSessionsTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+	require.InDelta(t, initialCanceledSessions, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
+
+	conn, chans = setup(1, newChannel)
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		close(chans)
+		return grpcstatus.Error(grpccodes.Canceled, "error")
+	})
+
+	require.InDelta(t, initialSessionsTotal+2, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
+}
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -22,7 +22,6 @@
 	channel     ssh.Channel
 	gitlabKeyId string
 	remoteAddr  string
-	success     bool
 
 	// State managed by the session
 	execCmd            string
@@ -42,11 +41,12 @@
 	ExitStatus uint32
 }
 
-func (s *session) handle(ctx context.Context, requests <-chan *ssh.Request) {
+func (s *session) handle(ctx context.Context, requests <-chan *ssh.Request) error {
 	ctxlog := log.ContextLogger(ctx)
 
 	ctxlog.Debug("session: handle: entering request loop")
 
+	var err error
 	for req := range requests {
 		sessionLog := ctxlog.WithFields(log.Fields{
 			"bytesize":   len(req.Payload),
@@ -58,12 +58,14 @@
 		var shouldContinue bool
 		switch req.Type {
 		case "env":
-			shouldContinue = s.handleEnv(ctx, req)
+			shouldContinue, err = s.handleEnv(ctx, req)
 		case "exec":
-			shouldContinue = s.handleExec(ctx, req)
+			shouldContinue, err = s.handleExec(ctx, req)
 		case "shell":
 			shouldContinue = false
-			s.exit(ctx, s.handleShell(ctx, req))
+			var status uint32
+			status, err = s.handleShell(ctx, req)
+			s.exit(ctx, status)
 		default:
 			// Ignore unknown requests but don't terminate the session
 			shouldContinue = true
@@ -84,15 +86,17 @@
 	}
 
 	ctxlog.Debug("session: handle: exiting request loop")
+
+	return err
 }
 
-func (s *session) handleEnv(ctx context.Context, req *ssh.Request) bool {
+func (s *session) handleEnv(ctx context.Context, req *ssh.Request) (bool, error) {
 	var accepted bool
 	var envRequest envRequest
 
 	if err := ssh.Unmarshal(req.Payload, &envRequest); err != nil {
 		log.ContextLogger(ctx).WithError(err).Error("session: handleEnv: failed to unmarshal request")
-		return false
+		return false, err
 	}
 
 	switch envRequest.Name {
@@ -113,23 +117,24 @@
 		ctx, log.Fields{"accepted": accepted, "env_request": envRequest},
 	).Debug("session: handleEnv: processed")
 
-	return true
+	return true, nil
 }
 
-func (s *session) handleExec(ctx context.Context, req *ssh.Request) bool {
+func (s *session) handleExec(ctx context.Context, req *ssh.Request) (bool, error) {
 	var execRequest execRequest
 	if err := ssh.Unmarshal(req.Payload, &execRequest); err != nil {
-		return false
+		return false, err
 	}
 
 	s.execCmd = execRequest.Command
 
-	s.exit(ctx, s.handleShell(ctx, req))
+	status, err := s.handleShell(ctx, req)
+	s.exit(ctx, status)
 
-	return false
+	return false, err
 }
 
-func (s *session) handleShell(ctx context.Context, req *ssh.Request) uint32 {
+func (s *session) handleShell(ctx context.Context, req *ssh.Request) (uint32, error) {
 	ctxlog := log.ContextLogger(ctx)
 
 	if req.WantReply {
@@ -157,7 +162,7 @@
 			s.toStderr(ctx, "Failed to parse command: %v\n", err.Error())
 		}
 		s.toStderr(ctx, "Unknown command: %v\n", s.execCmd)
-		return 128
+		return 128, err
 	}
 
 	cmdName := reflect.TypeOf(cmd).String()
@@ -165,12 +170,12 @@
 
 	if err := cmd.Execute(ctx); err != nil {
 		s.toStderr(ctx, "remote: ERROR: %v\n", err.Error())
-		return 1
+		return 1, err
 	}
 
 	ctxlog.Info("session: handleShell: command executed successfully")
 
-	return 0
+	return 0, nil
 }
 
 func (s *session) toStderr(ctx context.Context, format string, args ...interface{}) {
@@ -183,8 +188,6 @@
 	log.WithContextFields(ctx, log.Fields{"exit_status": status}).Info("session: exit: exiting")
 	req := exitStatusReq{ExitStatus: status}
 
-	s.success = status == 0
-
 	s.channel.CloseWrite()
 	s.channel.SendRequest("exit-status", false, ssh.Marshal(req))
 }
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -3,6 +3,7 @@
 import (
 	"bytes"
 	"context"
+	"errors"
 	"io"
 	"net/http"
 	"testing"
@@ -60,22 +61,26 @@
 	testCases := []struct {
 		desc                    string
 		payload                 []byte
+		expectedErr             error
 		expectedProtocolVersion string
 		expectedResult          bool
 	}{
 		{
 			desc:                    "invalid payload",
 			payload:                 []byte("invalid"),
+			expectedErr:             errors.New("ssh: unmarshal error for field Name of type envRequest"),
 			expectedProtocolVersion: "1",
 			expectedResult:          false,
 		}, {
 			desc:                    "valid payload",
 			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL", Value: "2"}),
+			expectedErr:             nil,
 			expectedProtocolVersion: "2",
 			expectedResult:          true,
 		}, {
 			desc:                    "valid payload with forbidden env var",
 			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL_ENV", Value: "2"}),
+			expectedErr:             nil,
 			expectedProtocolVersion: "1",
 			expectedResult:          true,
 		},
@@ -86,8 +91,11 @@
 			s := &session{gitProtocolVersion: "1"}
 			r := &ssh.Request{Payload: tc.payload}
 
-			require.Equal(t, s.handleEnv(context.Background(), r), tc.expectedResult)
-			require.Equal(t, s.gitProtocolVersion, tc.expectedProtocolVersion)
+			shouldContinue, err := s.handleEnv(context.Background(), r)
+
+			require.Equal(t, tc.expectedErr, err)
+			require.Equal(t, tc.expectedResult, shouldContinue)
+			require.Equal(t, tc.expectedProtocolVersion, s.gitProtocolVersion)
 		})
 	}
 }
@@ -96,23 +104,24 @@
 	testCases := []struct {
 		desc               string
 		payload            []byte
+		expectedErr        error
 		expectedExecCmd    string
 		sentRequestName    string
 		sentRequestPayload []byte
-		success            bool
 	}{
 		{
 			desc:            "invalid payload",
 			payload:         []byte("invalid"),
+			expectedErr:     errors.New("ssh: unmarshal error for field Command of type execRequest"),
 			expectedExecCmd: "",
 			sentRequestName: "",
 		}, {
 			desc:               "valid payload",
 			payload:            ssh.Marshal(execRequest{Command: "discover"}),
+			expectedErr:        nil,
 			expectedExecCmd:    "discover",
 			sentRequestName:    "exit-status",
 			sentRequestPayload: ssh.Marshal(exitStatusReq{ExitStatus: 0}),
-			success:            true,
 		},
 	}
 
@@ -129,47 +138,53 @@
 			}
 			r := &ssh.Request{Payload: tc.payload}
 
-			require.Equal(t, false, s.handleExec(context.Background(), r))
+			shouldContinue, err := s.handleExec(context.Background(), r)
+
+			require.Equal(t, tc.expectedErr, err)
+			require.Equal(t, false, shouldContinue)
 			require.Equal(t, tc.sentRequestName, f.sentRequestName)
 			require.Equal(t, tc.sentRequestPayload, f.sentRequestPayload)
-			require.Equal(t, tc.success, s.success)
 		})
 	}
 }
 
 func TestHandleShell(t *testing.T) {
 	testCases := []struct {
-		desc             string
-		cmd              string
-		errMsg           string
-		gitlabKeyId      string
-		expectedExitCode uint32
-		success          bool
+		desc              string
+		cmd               string
+		errMsg            string
+		gitlabKeyId       string
+		expectedErrString string
+		expectedExitCode  uint32
 	}{
 		{
-			desc:             "fails to parse command",
-			cmd:              `\`,
-			errMsg:           "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
-			gitlabKeyId:      "root",
-			expectedExitCode: 128,
+			desc:              "fails to parse command",
+			cmd:               `\`,
+			errMsg:            "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
+			gitlabKeyId:       "root",
+			expectedErrString: "Invalid SSH command: invalid command line string",
+			expectedExitCode:  128,
 		}, {
-			desc:             "specified command is unknown",
-			cmd:              "unknown-command",
-			errMsg:           "Unknown command: unknown-command\n",
-			gitlabKeyId:      "root",
-			expectedExitCode: 128,
+			desc:              "specified command is unknown",
+			cmd:               "unknown-command",
+			errMsg:            "Unknown command: unknown-command\n",
+			gitlabKeyId:       "root",
+			expectedErrString: "Disallowed command",
+			expectedExitCode:  128,
 		}, {
-			desc:             "fails to parse command",
-			cmd:              "discover",
-			gitlabKeyId:      "",
-			errMsg:           "remote: ERROR: Failed to get username: who='' is invalid\n",
-			expectedExitCode: 1,
+			desc:              "fails to parse command",
+			cmd:               "discover",
+			gitlabKeyId:       "",
+			errMsg:            "remote: ERROR: Failed to get username: who='' is invalid\n",
+			expectedErrString: "Failed to get username: who='' is invalid",
+			expectedExitCode:  1,
 		}, {
-			desc:             "fails to parse command",
-			cmd:              "discover",
-			errMsg:           "",
-			gitlabKeyId:      "root",
-			expectedExitCode: 0,
+			desc:              "fails to parse command",
+			cmd:               "discover",
+			errMsg:            "",
+			gitlabKeyId:       "root",
+			expectedErrString: "",
+			expectedExitCode:  0,
 		},
 	}
 
@@ -186,7 +201,13 @@
 			}
 			r := &ssh.Request{}
 
-			require.Equal(t, tc.expectedExitCode, s.handleShell(context.Background(), r))
+			exitCode, err := s.handleShell(context.Background(), r)
+
+			if tc.expectedErrString != "" {
+				require.Equal(t, tc.expectedErrString, err.Error())
+			}
+
+			require.Equal(t, tc.expectedExitCode, exitCode)
 			require.Equal(t, tc.errMsg, out.String())
 		})
 	}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -181,7 +181,7 @@
 	started := time.Now()
 	var establishSessionDuration float64
 	conn := newConnection(s.Config, remoteAddr, sconn)
-	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
+	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) error {
 		establishSessionDuration = time.Since(started).Seconds()
 		metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
 
@@ -192,11 +192,7 @@
 			remoteAddr:  remoteAddr,
 		}
 
-		metrics.SliSshdSessionsTotal.Inc()
-		session.handle(ctx, requests)
-		if !session.success {
-			metrics.SliSshdSessionsErrorsTotal.Inc()
-		}
+		return session.handle(ctx, requests)
 	})
 
 	reason := sconn.Wait()
# HG changeset patch
# User Georges Racinet on purity.racinet.fr <georges@racinet.fr>
# Date 1663185648 -7200
#      Wed Sep 14 22:00:48 2022 +0200
# Branch heptapod
# Node ID c19e92bccb5497c6d2c16280eaf2d695a5c48846
# Parent  7e62e4be19def9c1f1773e326480a9429210bb8d
Added tag heptapod-14.3.0 for changeset 7e62e4be19de

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -42,3 +42,4 @@
 bbf4eff5db1a60372d6a9828b15464d5207c1fbf heptapod-13.25.0
 df16c7bb5675b839c8b7cac2252379e32850a7ab heptapod-14.0.0
 98d781e90b1bb472b9c37ad5fbdbed541e1457c5 heptapod-14.2.0
+7e62e4be19def9c1f1773e326480a9429210bb8d heptapod-14.3.0
# HG changeset patch
# User Georges Racinet on purity.racinet.fr <georges@racinet.fr>
# Date 1663503515 -7200
#      Sun Sep 18 14:18:35 2022 +0200
# Branch heptapod
# Node ID fca0cc645e5bbe898f18befe0f31f51f091db81b
# Parent  c19e92bccb5497c6d2c16280eaf2d695a5c48846
Updated HEPTAPOD_VERSION to match forthcoming tag

diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,11 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 14.3.1
+
+- Filled in `HEPTAPOD_VERSION` (had been forgotten in 14.0 through
+  14.3.0)
+
 ## 14.3.0
 
 - Bump to upstream GitLab Shell v14.3.0 (GitLab 15.0 series)
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-13.25.0
+14.3.1
# HG changeset patch
# User Georges Racinet on purity.racinet.fr <georges@racinet.fr>
# Date 1663503524 -7200
#      Sun Sep 18 14:18:44 2022 +0200
# Branch heptapod
# Node ID 82e49acb60d6a2848eefa344c8f9c77fdfbc2426
# Parent  fca0cc645e5bbe898f18befe0f31f51f091db81b
Added tag heptapod-14.3.1 for changeset fca0cc645e5b

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -43,3 +43,4 @@
 df16c7bb5675b839c8b7cac2252379e32850a7ab heptapod-14.0.0
 98d781e90b1bb472b9c37ad5fbdbed541e1457c5 heptapod-14.2.0
 7e62e4be19def9c1f1773e326480a9429210bb8d heptapod-14.3.0
+fca0cc645e5bbe898f18befe0f31f51f091db81b heptapod-14.3.1
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1663792446 -7200
#      Wed Sep 21 22:34:06 2022 +0200
# Branch heptapod
# Node ID 7b25801e4afc9c3d6e7016fd220cce310db6bd1d
# Parent  82e49acb60d6a2848eefa344c8f9c77fdfbc2426
# Parent  2ab8e6717d3b7798734e1bac9d079c41f52d5b0a
Merged heptapod-stable branch into heptapod

Just to flush the numerous changesets about x.y being
the new stable.

No real changes.

# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1663931992 -7200
#      Fri Sep 23 13:19:52 2022 +0200
# Branch heptapod-stable
# Node ID 0b31292ecc0575dfcd23ee86046834b5a68cdd0d
# Parent  2ab8e6717d3b7798734e1bac9d079c41f52d5b0a
# Parent  7b25801e4afc9c3d6e7016fd220cce310db6bd1d
heptapod#697: Making 0.33 the new stable

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -60,7 +60,7 @@
   image: golang:${GO_VERSION}
   parallel:
     matrix:
-      - GO_VERSION: ["1.16", "1.17", "1.18"]
+      - GO_VERSION: ["1.17", "1.18"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
@@ -92,9 +92,6 @@
 gemnasium-dependency_scanning:
   rules: *workflow_rules
 
-bundler-audit-dependency_scanning:
-  rules: *workflow_rules
-
 # Secret Detection
 secret_detection:
   rules: *workflow_rules
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -1,4 +1,4 @@
-* @ashmckenzie @igor.drozdov @nick.thomas @patrickbajao
+* @ashmckenzie @igor.drozdov @patrickbajao
 
 [Documentation]
 *.md @aqualls
diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -40,3 +40,7 @@
 61814961ec7c871e9a081f4399536e5b8a62fabf heptapod-13.23.0
 eaa400f46c97a398037f0a72a5a30836f31a3586 heptapod-13.24.0
 bbf4eff5db1a60372d6a9828b15464d5207c1fbf heptapod-13.25.0
+df16c7bb5675b839c8b7cac2252379e32850a7ab heptapod-14.0.0
+98d781e90b1bb472b9c37ad5fbdbed541e1457c5 heptapod-14.2.0
+7e62e4be19def9c1f1773e326480a9429210bb8d heptapod-14.3.0
+fca0cc645e5bbe898f18befe0f31f51f091db81b heptapod-14.3.1
diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.8
+golang 1.17.9
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,39 @@
+v14.3.0
+
+- Remove deprecated bundler-audit !626
+- Wait until all Gitaly sessions are executed !624
+
+v14.2.0
+
+- Implement ClientKeepAlive option !622
+- build: bump go-proxyproto to 0.6.2 !610
+
+v14.1.1
+
+- Log the error that happens on sconn.Wait() !613
+
+v14.1.0
+
+- Make PROXY policy configurable !619
+- Exclude authentication errors from apdex !611
+- Fix check_ip argument when gitlab-sshd used with PROXY protocol !616
+- Use labkit for FIPS check !607
+
+v14.0.0
+
+- Always use Gitaly sidechannel connections !567
+
+v13.26.0
+
+- Add JWT token to GitLab Rails request !596
+- Drop go 1.16 support !601
+- Remove `self_signed_cert` option !602
+
+v13.25.2
+
+- Revert "Abort long-running unauthenticated SSH connections" !605
+- Bump Go to 1.17.9 for asdf users !600
+
 v13.25.1
 
 - Upgrade golang to 1.17.8 !591
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,25 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 14.3.1
+
+- Filled in `HEPTAPOD_VERSION` (had been forgotten in 14.0 through
+  14.3.0)
+
+## 14.3.0
+
+- Bump to upstream GitLab Shell v14.3.0 (GitLab 15.0 series)
+
+## 14.2.0
+
+- Jump to upstream GitLab Shell v14.2.0 (pre-GitLab 15.0)
+
+## 14.0.0
+
+- Jump to upstream GitLab Shell v14.0.0 (pre-GitLab 15.0)
+- `gitlab-shell -version` now displays the build time as expected
+  instead of an empty string, giving a trailing dash (e.g., `hpd-13-25-`).
+
 ## 13.25.0
 
 - Jump to upstream GitLab Shell v13.25.1 (GitLab 14.10 series)
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-13.25.0
+14.3.1
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -3,10 +3,15 @@
 FIPS_MODE ?= 0
 GO_SOURCES := $(shell find . -name '*.go')
 VERSION_STRING := $(shell ./compute_version || echo unknown)
+BUILD_TIME := $(shell date -u +%Y%m%d.%H%M%S)
 BUILD_TAGS := tracer_static tracer_static_jaeger continuous_profiler_stackdriver
 
 ifeq (${FIPS_MODE}, 1)
-    BUILD_TAGS += boringcrypto
+    # boringcrypto tag is added automatically by golang-fips compiler
+    BUILD_TAGS += fips
+    # If the golang-fips compiler is built with CGO_ENABLED=0, this needs to be
+    # explicitly switched on.
+    export CGO_ENABLED=1
 endif
 
 GOBUILD_FLAGS := -ldflags "-X main.Version=$(VERSION_STRING) -X main.BuildTime=$(BUILD_TIME)" -tags "$(BUILD_TAGS)" -mod=mod
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.25.1
+14.3.0
diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -10,13 +10,19 @@
 	"path"
 	"strings"
 	"testing"
+	"time"
 
+	"github.com/golang-jwt/jwt/v4"
 	"github.com/stretchr/testify/require"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
 
+var (
+	secret = []byte("sssh, it's a secret")
+)
+
 func TestClients(t *testing.T) {
 	testhelper.PrepareTestRootDir(t)
 
@@ -57,12 +63,10 @@
 		t.Run(tc.desc, func(t *testing.T) {
 			url := tc.server(t, buildRequests(t, tc.relativeURLRoot))
 
-			secret := "sssh, it's a secret"
-
-			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", false, 1, nil)
+			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", 1, nil)
 			require.NoError(t, err)
 
-			client, err := NewGitlabNetClient("", "", secret, httpClient)
+			client, err := NewGitlabNetClient("", "", string(secret), httpClient)
 			require.NoError(t, err)
 
 			testBrokenRequest(t, client)
@@ -71,6 +75,7 @@
 			testMissing(t, client)
 			testErrorMessage(t, client)
 			testAuthenticationHeader(t, client)
+			testJWTAuthenticationHeader(t, client)
 		})
 	}
 }
@@ -160,7 +165,7 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, "sssh, it's a secret", string(header))
+		require.Equal(t, secret, header)
 	})
 
 	t.Run("Authentication headers for POST", func(t *testing.T) {
@@ -175,7 +180,44 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, "sssh, it's a secret", string(header))
+		require.Equal(t, secret, header)
+	})
+}
+
+func testJWTAuthenticationHeader(t *testing.T, client *GitlabNetClient) {
+	verifyJWTToken := func(t *testing.T, response *http.Response) {
+		responseBody, err := io.ReadAll(response.Body)
+		require.NoError(t, err)
+
+		claims := &jwt.RegisteredClaims{}
+		token, err := jwt.ParseWithClaims(string(responseBody), claims, func(token *jwt.Token) (interface{}, error) {
+			return secret, nil
+		})
+		require.NoError(t, err)
+		require.True(t, token.Valid)
+		require.Equal(t, "gitlab-shell", claims.Issuer)
+		require.WithinDuration(t, time.Now().Truncate(time.Second), claims.IssuedAt.Time, time.Second)
+		require.WithinDuration(t, time.Now().Truncate(time.Second).Add(time.Minute), claims.ExpiresAt.Time, time.Second)
+	}
+
+	t.Run("JWT authentication headers for GET", func(t *testing.T) {
+		response, err := client.Get(context.Background(), "/jwt_auth")
+		require.NoError(t, err)
+		require.NotNil(t, response)
+
+		defer response.Body.Close()
+
+		verifyJWTToken(t, response)
+	})
+
+	t.Run("JWT authentication headers for POST", func(t *testing.T) {
+		response, err := client.Post(context.Background(), "/jwt_auth", map[string]string{})
+		require.NoError(t, err)
+		require.NotNil(t, response)
+
+		defer response.Body.Close()
+
+		verifyJWTToken(t, response)
 	})
 }
 
@@ -209,6 +251,12 @@
 			},
 		},
 		{
+			Path: "/api/v4/internal/jwt_auth",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				fmt.Fprint(w, r.Header.Get(apiSecretHeaderName))
+			},
+		},
+		{
 			Path: "/api/v4/internal/error",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -11,13 +11,18 @@
 	"strings"
 	"time"
 
+	"github.com/golang-jwt/jwt/v4"
+
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
 const (
-	internalApiPath  = "/api/v4/internal"
-	secretHeaderName = "Gitlab-Shared-Secret"
-	defaultUserAgent = "GitLab-Shell"
+	internalApiPath     = "/api/v4/internal"
+	secretHeaderName    = "Gitlab-Shared-Secret"
+	apiSecretHeaderName = "Gitlab-Shell-Api-Request"
+	defaultUserAgent    = "GitLab-Shell"
+	jwtTTL              = time.Minute
+	jwtIssuer           = "gitlab-shell"
 )
 
 type ErrorResponse struct {
@@ -121,10 +126,22 @@
 	if user != "" && password != "" {
 		request.SetBasicAuth(user, password)
 	}
+	secretBytes := []byte(c.secret)
 
-	encodedSecret := base64.StdEncoding.EncodeToString([]byte(c.secret))
+	encodedSecret := base64.StdEncoding.EncodeToString(secretBytes)
 	request.Header.Set(secretHeaderName, encodedSecret)
 
+	claims := jwt.RegisteredClaims{
+		Issuer:    jwtIssuer,
+		IssuedAt:  jwt.NewNumericDate(time.Now()),
+		ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtTTL)),
+	}
+	tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secretBytes)
+	if err != nil {
+		return nil, err
+	}
+	request.Header.Set(apiSecretHeaderName, tokenString)
+
 	request.Header.Add("Content-Type", "application/json")
 	request.Header.Add("User-Agent", c.userAgent)
 	request.Close = true
diff --git a/client/httpclient.go b/client/httpclient.go
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -14,7 +14,6 @@
 	"time"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
-	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/tracing"
 )
 
@@ -70,17 +69,8 @@
 	return nil
 }
 
-// Deprecated: use NewHTTPClientWithOpts - https://gitlab.com/gitlab-org/gitlab-shell/-/issues/484
-func NewHTTPClient(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64) *HttpClient {
-	c, err := NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath, selfSignedCert, readTimeoutSeconds, nil)
-	if err != nil {
-		log.WithError(err).Error("new http client with opts")
-	}
-	return c
-}
-
 // NewHTTPClientWithOpts builds an HTTP client using the provided options
-func NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, selfSignedCert bool, readTimeoutSeconds uint64, opts []HTTPClientOpt) (*HttpClient, error) {
+func NewHTTPClientWithOpts(gitlabURL, gitlabRelativeURLRoot, caFile, caPath string, readTimeoutSeconds uint64, opts []HTTPClientOpt) (*HttpClient, error) {
 	var transport *http.Transport
 	var host string
 	var err error
@@ -103,7 +93,7 @@
 			opt(hcc)
 		}
 
-		transport, host, err = buildHttpsTransport(*hcc, selfSignedCert, gitlabURL)
+		transport, host, err = buildHttpsTransport(*hcc, gitlabURL)
 		if err != nil {
 			return nil, err
 		}
@@ -140,7 +130,7 @@
 	return transport, host
 }
 
-func buildHttpsTransport(hcc httpClientCfg, selfSignedCert bool, gitlabURL string) (*http.Transport, string, error) {
+func buildHttpsTransport(hcc httpClientCfg, gitlabURL string) (*http.Transport, string, error) {
 	certPool, err := x509.SystemCertPool()
 
 	if err != nil {
@@ -162,12 +152,8 @@
 		}
 	}
 	tlsConfig := &tls.Config{
-		RootCAs: certPool,
-		// The self_signed_cert config setting is deprecated
-		// The field and its usage is going to be removed in
-		// https://gitlab.com/gitlab-org/gitlab-shell/-/issues/541
-		InsecureSkipVerify: selfSignedCert,
-		MinVersion:         tls.VersionTLS12,
+		RootCAs:    certPool,
+		MinVersion: tls.VersionTLS12,
 	}
 
 	if hcc.HaveCertAndKey() {
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -17,7 +17,7 @@
 func TestReadTimeout(t *testing.T) {
 	expectedSeconds := uint64(300)
 
-	client, err := NewHTTPClientWithOpts("http://localhost:3000", "", "", "", false, expectedSeconds, nil)
+	client, err := NewHTTPClientWithOpts("http://localhost:3000", "", "", "", expectedSeconds, nil)
 	require.NoError(t, err)
 
 	require.NotNil(t, client)
@@ -123,7 +123,7 @@
 func setup(t *testing.T, username, password string, requests []testserver.TestRequestHandler) *GitlabNetClient {
 	url := testserver.StartHttpServer(t, requests)
 
-	httpClient, err := NewHTTPClientWithOpts(url, "", "", "", false, 1, nil)
+	httpClient, err := NewHTTPClientWithOpts(url, "", "", "", 1, nil)
 	require.NoError(t, err)
 
 	client, err := NewGitlabNetClient(username, password, "", httpClient)
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -18,7 +18,6 @@
 	testCases := []struct {
 		desc                                        string
 		caFile, caPath                              string
-		selfSigned                                  bool
 		clientCAPath, clientCertPath, clientKeyPath string // used for TLS client certs
 	}{
 		{
@@ -31,9 +30,8 @@
 			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
 		},
 		{
-			desc:       "Invalid cert with self signed cert option enabled",
-			caFile:     path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
-			selfSigned: true,
+			desc:   "Invalid cert with self signed cert option enabled",
+			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
 		},
 		{
 			desc:   "Client certs with CA",
@@ -48,7 +46,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client, err := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath, tc.selfSigned)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, tc.clientCAPath, tc.clientCertPath, tc.clientKeyPath)
 			require.NoError(t, err)
 
 			response, err := client.Get(context.Background(), "/hello")
@@ -95,7 +93,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			client, err := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "", false)
+			client, err := setupWithRequests(t, tc.caFile, tc.caPath, "", "", "")
 			if tc.expectedCaFileNotFound {
 				require.Error(t, err)
 				require.ErrorIs(t, err, ErrCafileNotFound)
@@ -109,7 +107,7 @@
 	}
 }
 
-func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string, selfSigned bool) (*GitlabNetClient, error) {
+func setupWithRequests(t *testing.T, caFile, caPath, clientCAPath, clientCertPath, clientKeyPath string) (*GitlabNetClient, error) {
 	testhelper.PrepareTestRootDir(t)
 
 	requests := []testserver.TestRequestHandler{
@@ -130,7 +128,7 @@
 		opts = append(opts, WithClientCert(clientCertPath, clientKeyPath))
 	}
 
-	httpClient, err := NewHTTPClientWithOpts(url, "", caFile, caPath, selfSigned, 1, opts)
+	httpClient, err := NewHTTPClientWithOpts(url, "", caFile, caPath, 1, opts)
 	if err != nil {
 		return nil, err
 	}
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -8,10 +8,10 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
+	"gitlab.com/gitlab-org/labkit/fips"
 	"gitlab.com/gitlab-org/labkit/log"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/boring"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -74,7 +74,7 @@
 	cmdName := reflect.TypeOf(cmd).String()
 	ctxlog := log.ContextLogger(ctx)
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("gitlab-shell: main: executing command")
-	boring.CheckBoring()
+	fips.Check()
 
 	if err := cmd.Execute(ctx); err != nil {
 		ctxlog.WithError(err).Warn("gitlab-shell: main: command execution failed")
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -43,11 +43,6 @@
 #  ca_file: /etc/ssl/cert.pem
 #  ca_path: /etc/pki/tls/certs
 #
-#  The self_signed_cert option is deprecated
-#  When it's set to true, any certificate is accepted, which may make machine-in-the-middle attack possible
-#  Certificates specified in ca_file and ca_path are trusted anyway even if they are self-signed
-#  Issue: https://gitlab.com/gitlab-org/gitlab-shell/-/issues/120
-  self_signed_cert: false
 
 # File used as authorized_keys for gitlab user
 auth_file: "/home/git/.ssh/authorized_keys"
@@ -90,18 +85,21 @@
   # Set to true if gitlab-sshd is being fronted by a load balancer that implements
   # the PROXY protocol.
   proxy_protocol: false
+  # Proxy protocol policy ("use", "require", "reject", "ignore"), "use" is the default value
+  # Values: https://github.com/pires/go-proxyproto/blob/195fedcfbfc1be163f3a0d507fac1709e9d81fed/policy.go#L20
+  proxy_policy: "use"
   # Address which the server listens on HTTP for monitoring/health checks. Defaults to localhost:9122.
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
+  # Sets an interval after which server will send keepalive message to a client
+  client_alive_interval: 15
   # The server waits for this time (in seconds) for the ongoing connections to complete before shutting down. Defaults to 10.
   grace_period: 10
   # The endpoint that returns 200 OK if the server is ready to receive incoming connections; otherwise, it returns 503 Service Unavailable. Defaults to "/start".
   readiness_probe: "/start"
   # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
   liveness_probe: "/health"
-  # The server disconnects after this time (in seconds) if the user has not successfully logged in. Defaults to 60.
-  login_grace_time: 60
   # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -1,22 +1,84 @@
 module gitlab.com/gitlab-org/gitlab-shell
 
-go 1.16
+go 1.17
 
 require (
+	github.com/golang-jwt/jwt/v4 v4.4.1
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
 	github.com/mattn/go-shellwords v1.0.11
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
-	github.com/pires/go-proxyproto v0.6.1
-	github.com/prometheus/client_golang v1.11.0
+	github.com/pires/go-proxyproto v0.6.2
+	github.com/prometheus/client_golang v1.12.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
-	gitlab.com/gitlab-org/labkit v1.12.0
-	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
+	gitlab.com/gitlab-org/labkit v1.14.0
+	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/grpc v1.40.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
+require (
+	cloud.google.com/go v0.92.2 // indirect
+	cloud.google.com/go/profiler v0.1.0 // indirect
+	cloud.google.com/go/trace v0.1.0 // indirect
+	contrib.go.opencensus.io/exporter/stackdriver v0.13.8 // indirect
+	github.com/DataDog/datadog-go v4.4.0+incompatible // indirect
+	github.com/DataDog/sketches-go v1.0.0 // indirect
+	github.com/Microsoft/go-winio v0.5.0 // indirect
+	github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
+	github.com/aws/aws-sdk-go v1.38.35 // indirect
+	github.com/beevik/ntp v0.3.0 // indirect
+	github.com/beorn7/perks v1.0.1 // indirect
+	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/davecgh/go-spew v1.1.1 // indirect
+	github.com/go-ole/go-ole v1.2.4 // indirect
+	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/google/go-cmp v0.5.6 // indirect
+	github.com/google/pprof v0.0.0-20210804190019-f964ff605595 // indirect
+	github.com/google/uuid v1.2.0 // indirect
+	github.com/googleapis/gax-go/v2 v2.0.5 // indirect
+	github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 // indirect
+	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 // indirect
+	github.com/lightstep/lightstep-tracer-go v0.25.0 // indirect
+	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
+	github.com/oklog/ulid/v2 v2.0.2 // indirect
+	github.com/opentracing/opentracing-go v1.2.0 // indirect
+	github.com/philhofer/fwd v1.1.1 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/prometheus/client_model v0.2.0 // indirect
+	github.com/prometheus/common v0.32.1 // indirect
+	github.com/prometheus/procfs v0.7.3 // indirect
+	github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect
+	github.com/shirou/gopsutil/v3 v3.21.2 // indirect
+	github.com/sirupsen/logrus v1.8.1 // indirect
+	github.com/tinylib/msgp v1.1.2 // indirect
+	github.com/tklauser/go-sysconf v0.3.4 // indirect
+	github.com/tklauser/numcpus v0.2.1 // indirect
+	github.com/uber/jaeger-client-go v2.29.1+incompatible // indirect
+	github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
+	go.opencensus.io v0.23.0 // indirect
+	go.uber.org/atomic v1.7.0 // indirect
+	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
+	golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a // indirect
+	golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
+	golang.org/x/text v0.3.7 // indirect
+	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
+	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+	google.golang.org/api v0.54.0 // indirect
+	google.golang.org/appengine v1.6.7 // indirect
+	google.golang.org/genproto v0.0.0-20210813162853-db860fec028c // indirect
+	google.golang.org/protobuf v1.27.1 // indirect
+	gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // indirect
+	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
+)
+
 replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -165,8 +165,9 @@
 github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -255,12 +256,13 @@
 github.com/getsentry/sentry-go v0.5.1/go.mod h1:B8H7x8TYDPkeWPRzGpIiFO97LZP6rL8A3hEt8lUItMw=
 github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
 github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
-github.com/getsentry/sentry-go v0.11.0/go.mod h1:KBQIxiZAetw62Cj8Ri964vAEWVdgfaUCn30Q3bCvANo=
+github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
 github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
+github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
 github.com/git-lfs/git-lfs v1.5.1-0.20210304194248-2e1d981afbe3/go.mod h1:8Xqs4mqL7o6xEnaXckIgELARTeK7RYtm3pBab7S79Js=
 github.com/git-lfs/gitobj/v2 v2.0.1/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
 github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
@@ -294,6 +296,7 @@
 github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
+github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@@ -316,6 +319,10 @@
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
+github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
+github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -529,6 +536,7 @@
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jteeuwen/go-bindata v3.0.8-0.20180305030458-6025e8de665b+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
@@ -575,6 +583,7 @@
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
+github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -601,6 +610,8 @@
 github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -609,6 +620,7 @@
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-shellwords v0.0.0-20190425161501-2444a32a19f4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
@@ -639,6 +651,7 @@
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/montanaflynn/stats v0.0.0-20151014174947-eeaced052adb/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -704,8 +717,8 @@
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
 github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
-github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
-github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
+github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -721,8 +734,9 @@
 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
-github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
+github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -735,15 +749,17 @@
 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
-github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
+github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
+github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
@@ -844,6 +860,7 @@
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
+github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
 github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
 github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
@@ -878,8 +895,8 @@
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
 gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
 gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
-gitlab.com/gitlab-org/labkit v1.12.0 h1:XVwGqXfHrhEr2bzZbYy/eA7xulAeyvk9HB3Q7nH2H4I=
-gitlab.com/gitlab-org/labkit v1.12.0/go.mod h1:tnvyKiPF/Y0MeBy+ACYWRWexOEQk6PTRGUc9GstcnXc=
+gitlab.com/gitlab-org/labkit v1.14.0 h1:LSrvHgybidPyH8fHnsy1GBghrLR4kFObFrtZwUfCgAI=
+gitlab.com/gitlab-org/labkit v1.14.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
@@ -1008,6 +1025,8 @@
 golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -1109,6 +1128,7 @@
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1123,8 +1143,11 @@
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1134,12 +1157,14 @@
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/heptapod-ci.yml b/heptapod-ci.yml
--- a/heptapod-ci.yml
+++ b/heptapod-ci.yml
@@ -43,7 +43,7 @@
   image: golang:${GO_VERSION}
   parallel:
     matrix:
-      - GO_VERSION: ["1.16", "1.17", "1.18"]
+      - GO_VERSION: ["1.17", "1.18"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
diff --git a/internal/boring/boring.go b/internal/boring/boring.go
deleted file mode 100644
--- a/internal/boring/boring.go
+++ /dev/null
@@ -1,23 +0,0 @@
-//go:build boringcrypto
-// +build boringcrypto
-
-package boring
-
-import (
-	"crypto/boring"
-
-	"gitlab.com/gitlab-org/labkit/log"
-)
-
-// CheckBoring checks whether FIPS crypto has been enabled. For the FIPS Go
-// compiler in https://github.com/golang-fips/go, this requires that:
-//
-// 1. The kernel has FIPS enabled (e.g. `/proc/sys/crypto/fips_enabled` is 1).
-// 2. A system OpenSSL can be dynamically loaded via ldopen().
-func CheckBoring() {
-	if boring.Enabled() {
-		log.Info("FIPS mode is enabled. Using an external SSL library.")
-		return
-	}
-	log.Info("Gitaly was compiled with FIPS mode, but an external SSL library was not enabled.")
-}
diff --git a/internal/boring/notboring.go b/internal/boring/notboring.go
deleted file mode 100644
--- a/internal/boring/notboring.go
+++ /dev/null
@@ -1,9 +0,0 @@
-//go:build !boringcrypto
-// +build !boringcrypto
-
-package boring
-
-// CheckBoring does nothing when the boringcrypto tag is not in the
-// build.
-func CheckBoring() {
-}
diff --git a/internal/command/receivepack/gitalycall_test.go b/internal/command/receivepack/gitalycall_test.go
--- a/internal/command/receivepack/gitalycall_test.go
+++ b/internal/command/receivepack/gitalycall_test.go
@@ -57,8 +57,10 @@
 			args.GitlabKeyId = tc.keyId
 		}
 
+		cfg := &config.Config{GitlabUrl: url}
+		cfg.GitalyClient.InitSidechannelRegistry(context.Background())
 		cmd := &Command{
-			Config:     &config.Config{GitlabUrl: url},
+			Config:     cfg,
 			Args:       args,
 			ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 		}
diff --git a/internal/command/uploadarchive/gitalycall_test.go b/internal/command/uploadarchive/gitalycall_test.go
--- a/internal/command/uploadarchive/gitalycall_test.go
+++ b/internal/command/uploadarchive/gitalycall_test.go
@@ -41,8 +41,10 @@
 		Env:         env,
 	}
 
+	cfg := &config.Config{GitlabUrl: url}
+	cfg.GitalyClient.InitSidechannelRegistry(context.Background())
 	cmd := &Command{
-		Config:     &config.Config{GitlabUrl: url},
+		Config:     cfg,
 		Args:       args,
 		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
 	}
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -15,24 +15,7 @@
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
 	gc := handler.NewGitalyCommand(c.Config, string(commandargs.UploadPack), response)
 
-	if response.Gitaly.UseSidechannel {
-		request := &pb.SSHUploadPackWithSidechannelRequest{
-			Repository:       &response.Gitaly.Repo,
-			GitProtocol:      c.Args.Env.GitProtocolVersion,
-			GitConfigOptions: response.GitConfigOptions,
-		}
-
-		return gc.RunGitalyCommand(ctx, func(ctx context.Context, conn *grpc.ClientConn) (int32, error) {
-			ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
-			defer cancel()
-
-			registry := c.Config.GitalyClient.SidechannelRegistry
-			rw := c.ReadWriter
-			return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
-		})
-	}
-
-	request := &pb.SSHUploadPackRequest{
+	request := &pb.SSHUploadPackWithSidechannelRequest{
 		Repository:       &response.Gitaly.Repo,
 		GitProtocol:      c.Args.Env.GitProtocolVersion,
 		GitConfigOptions: response.GitConfigOptions,
@@ -42,7 +25,8 @@
 		ctx, cancel := gc.PrepareContext(ctx, request.Repository, c.Args.Env)
 		defer cancel()
 
+		registry := c.Config.GitalyClient.SidechannelRegistry
 		rw := c.ReadWriter
-		return client.UploadPack(ctx, conn, rw.In, rw.Out, rw.ErrOut, request)
+		return client.UploadPackWithSidechannel(ctx, conn, registry, rw.In, rw.Out, rw.ErrOut, request)
 	})
 }
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -41,62 +41,6 @@
 		Env:         env,
 	}
 
-	cmd := &Command{
-		Config:     &config.Config{GitlabUrl: url},
-		Args:       args,
-		ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input},
-	}
-
-	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
-	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
-
-	err := cmd.Execute(ctx)
-	require.NoError(t, err)
-
-	require.Equal(t, "UploadPack: "+repo, output.String())
-
-	for k, v := range map[string]string{
-		"gitaly-feature-cache_invalidator":        "true",
-		"gitaly-feature-inforef_uploadpack_cache": "false",
-		"x-gitlab-client-name":                    "gitlab-shell-tests-git-upload-pack",
-		"key_id":                                  "123",
-		"user_id":                                 "1",
-		"remote_ip":                               "127.0.0.1",
-		"key_type":                                "key",
-	} {
-		actual := testServer.ReceivedMD[k]
-		require.Len(t, actual, 1)
-		require.Equal(t, v, actual[0])
-	}
-	require.Empty(t, testServer.ReceivedMD["some-other-ff"])
-	require.Equal(t, testServer.ReceivedMD["x-gitlab-correlation-id"][0], "a-correlation-id")
-}
-
-func TestUploadPack_withSidechannel(t *testing.T) {
-	gitalyAddress, testServer := testserver.StartGitalyServer(t)
-
-	requests := requesthandlers.BuildAllowedWithGitalyHandlersWithSidechannel(t, gitalyAddress)
-	url := testserver.StartHttpServer(t, requests)
-
-	output := &bytes.Buffer{}
-	input := &bytes.Buffer{}
-
-	userId := "1"
-	repo := "group/repo"
-
-	env := sshenv.Env{
-		IsSSHConnection: true,
-		OriginalCommand: "git-upload-pack " + repo,
-		RemoteAddr:      "127.0.0.1",
-	}
-
-	args := &commandargs.Shell{
-		GitlabKeyId: userId,
-		CommandType: commandargs.UploadPack,
-		SshArgs:     []string{"git-upload-pack", repo},
-		Env:         env,
-	}
-
 	ctx := correlation.ContextWithCorrelation(context.Background(), "a-correlation-id")
 	ctx = correlation.ContextWithClientName(ctx, "gitlab-shell-tests")
 
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -28,15 +28,16 @@
 )
 
 type ServerConfig struct {
-	Listen                  string   `yaml:"listen,omitempty"`
-	ProxyProtocol           bool     `yaml:"proxy_protocol,omitempty"`
-	WebListen               string   `yaml:"web_listen,omitempty"`
-	ConcurrentSessionsLimit int64    `yaml:"concurrent_sessions_limit,omitempty"`
-	LoginGraceTimeSeconds   uint64   `yaml:"login_grace_time"`
-	GracePeriodSeconds      uint64   `yaml:"grace_period"`
-	ReadinessProbe          string   `yaml:"readiness_probe"`
-	LivenessProbe           string   `yaml:"liveness_probe"`
-	HostKeyFiles            []string `yaml:"host_key_files,omitempty"`
+	Listen                     string   `yaml:"listen,omitempty"`
+	ProxyProtocol              bool     `yaml:"proxy_protocol,omitempty"`
+	ProxyPolicy                string   `yaml:"proxy_policy,omitempty"`
+	WebListen                  string   `yaml:"web_listen,omitempty"`
+	ConcurrentSessionsLimit    int64    `yaml:"concurrent_sessions_limit,omitempty"`
+	ClientAliveIntervalSeconds int64    `yaml:"client_alive_interval,omitempty"`
+	GracePeriodSeconds         uint64   `yaml:"grace_period"`
+	ReadinessProbe             string   `yaml:"readiness_probe"`
+	LivenessProbe              string   `yaml:"liveness_probe"`
+	HostKeyFiles               []string `yaml:"host_key_files,omitempty"`
 }
 
 type HttpSettingsConfig struct {
@@ -45,7 +46,6 @@
 	ReadTimeoutSeconds uint64 `yaml:"read_timeout"`
 	CaFile             string `yaml:"ca_file"`
 	CaPath             string `yaml:"ca_path"`
-	SelfSignedCert     bool   `yaml:"self_signed_cert"`
 }
 
 type MercurialConfig struct {
@@ -103,13 +103,13 @@
 	}
 
 	DefaultServerConfig = ServerConfig{
-		Listen:                  "[::]:22",
-		WebListen:               "localhost:9122",
-		ConcurrentSessionsLimit: 10,
-		LoginGraceTimeSeconds:   60,
-		GracePeriodSeconds:      10,
-		ReadinessProbe:          "/start",
-		LivenessProbe:           "/health",
+		Listen:                     "[::]:22",
+		WebListen:                  "localhost:9122",
+		ConcurrentSessionsLimit:    10,
+		GracePeriodSeconds:         10,
+		ClientAliveIntervalSeconds: 15,
+		ReadinessProbe:             "/start",
+		LivenessProbe:              "/health",
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
@@ -118,8 +118,8 @@
 	}
 )
 
-func (sc *ServerConfig) LoginGraceTime() time.Duration {
-	return time.Duration(sc.LoginGraceTimeSeconds) * time.Second
+func (sc *ServerConfig) ClientAliveInterval() time.Duration {
+	return time.Duration(sc.ClientAliveIntervalSeconds) * time.Second
 }
 
 func (sc *ServerConfig) GracePeriod() time.Duration {
@@ -139,7 +139,6 @@
 			c.GitlabRelativeURLRoot,
 			c.HttpSettings.CaFile,
 			c.HttpSettings.CaPath,
-			c.HttpSettings.SelfSignedCert,
 			c.HttpSettings.ReadTimeoutSeconds,
 			nil,
 		)
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -39,7 +39,7 @@
 	require.NoError(t, err)
 
 	var actualNames []string
-	for _, m := range ms[0:9] {
+	for _, m := range ms[0:10] {
 		actualNames = append(actualNames, m.GetName())
 	}
 
@@ -47,6 +47,7 @@
 		"gitlab_shell_http_in_flight_requests",
 		"gitlab_shell_http_request_duration_seconds",
 		"gitlab_shell_http_requests_total",
+		"gitlab_shell_sshd_canceled_sessions",
 		"gitlab_shell_sshd_concurrent_limited_sessions_total",
 		"gitlab_shell_sshd_in_flight_connections",
 		"gitlab_shell_sshd_session_duration_seconds",
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
--- a/internal/gitaly/gitaly.go
+++ b/internal/gitaly/gitaly.go
@@ -21,10 +21,9 @@
 )
 
 type Command struct {
-	ServiceName     string
-	Address         string
-	Token           string
-	DialSidechannel bool
+	ServiceName string
+	Address     string
+	Token       string
 }
 
 type connectionsCache struct {
@@ -125,9 +124,5 @@
 		)
 	}
 
-	if cmd.DialSidechannel {
-		return client.DialSidechannel(ctx, cmd.Address, c.SidechannelRegistry, connOpts)
-	}
-
-	return client.DialContext(ctx, cmd.Address, connOpts)
+	return client.DialSidechannel(ctx, cmd.Address, c.SidechannelRegistry, connOpts)
 }
diff --git a/internal/gitaly/gitaly_test.go b/internal/gitaly/gitaly_test.go
--- a/internal/gitaly/gitaly_test.go
+++ b/internal/gitaly/gitaly_test.go
@@ -13,7 +13,7 @@
 func TestPrometheusMetrics(t *testing.T) {
 	metrics.GitalyConnectionsTotal.Reset()
 
-	c := &Client{}
+	c := newClient()
 
 	cmd := Command{ServiceName: "git-upload-pack", Address: "tcp://localhost:9999"}
 	c.newConnection(context.Background(), cmd)
@@ -31,7 +31,7 @@
 }
 
 func TestCachedConnections(t *testing.T) {
-	c := &Client{}
+	c := newClient()
 
 	require.Len(t, c.cache.connections, 0)
 
@@ -51,3 +51,9 @@
 	require.NoError(t, err)
 	require.Len(t, c.cache.connections, 2)
 }
+
+func newClient() *Client {
+	c := &Client{}
+	c.InitSidechannelRegistry(context.Background())
+	return c
+}
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -3,6 +3,7 @@
 import (
 	"context"
 	"fmt"
+	"net"
 	"net/http"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
@@ -32,11 +33,10 @@
 }
 
 type Gitaly struct {
-	Repo           pb.Repository     `json:"repository"`
-	Address        string            `json:"address"`
-	Token          string            `json:"token"`
-	Features       map[string]string `json:"features"`
-	UseSidechannel bool              `json:"use_sidechannel"`
+	Repo     pb.Repository     `json:"repository"`
+	Address  string            `json:"address"`
+	Token    string            `json:"token"`
+	Features map[string]string `json:"features"`
 }
 
 type CustomPayloadData struct {
@@ -86,7 +86,7 @@
 		request.KeyId = args.GitlabKeyId
 	}
 
-	request.CheckIp = args.Env.RemoteAddr
+	request.CheckIp = parseIP(args.Env.RemoteAddr)
 
 	response, err := c.client.Post(ctx, "/allowed", request)
 	if err != nil {
@@ -117,3 +117,18 @@
 func (r *Response) IsCustomAction() bool {
 	return r.StatusCode == http.StatusMultipleChoices
 }
+
+func parseIP(remoteAddr string) string {
+	// The remoteAddr field can be filled by:
+	// 1. An IP address via the SSH_CONNECTION environment variable
+	// 2. A host:port combination via the PROXY protocol
+	ip, _, err := net.SplitHostPort(remoteAddr)
+
+	// If we don't have a port or can't parse this address for some reason,
+	// just return the original string.
+	if err != nil {
+		return remoteAddr
+	}
+
+	return ip
+}
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -14,6 +14,7 @@
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
 )
 
@@ -86,41 +87,6 @@
 	}
 }
 
-func TestSidechannelFlag(t *testing.T) {
-	okResponse := testResponse{body: responseBody(t, "allowed_sidechannel.json"), status: http.StatusOK}
-	client := setup(t,
-		map[string]testResponse{"first": okResponse},
-		map[string]testResponse{"1": okResponse},
-	)
-
-	testCases := []struct {
-		desc string
-		args *commandargs.Shell
-		who  string
-	}{
-		{
-			desc: "Provide key id within the request",
-			args: &commandargs.Shell{GitlabKeyId: "1"},
-			who:  "key-1",
-		}, {
-			desc: "Provide username within the request",
-			args: &commandargs.Shell{GitlabUsername: "first"},
-			who:  "user-1",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.desc, func(t *testing.T) {
-			result, err := client.Verify(context.Background(), tc.args, uploadPackAction, repo)
-			require.NoError(t, err)
-
-			response := buildExpectedResponse(tc.who)
-			response.Gitaly.UseSidechannel = true
-			require.Equal(t, response, result)
-		})
-	}
-}
-
 func TestGeoPushGetCustomAction(t *testing.T) {
 	client := setup(t, map[string]testResponse{
 		"custom": {
@@ -215,6 +181,51 @@
 	}
 }
 
+func TestCheckIP(t *testing.T) {
+	testCases := []struct {
+		desc            string
+		remoteAddr      string
+		expectedCheckIp string
+	}{
+		{
+			desc:            "IPv4 address",
+			remoteAddr:      "18.245.0.42",
+			expectedCheckIp: "18.245.0.42",
+		},
+		{
+			desc:            "IPv6 address",
+			remoteAddr:      "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+			expectedCheckIp: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+		},
+		{
+			desc:            "Host and port",
+			remoteAddr:      "18.245.0.42:6345",
+			expectedCheckIp: "18.245.0.42",
+		},
+		{
+			desc:            "IPv6 host and port",
+			remoteAddr:      "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80",
+			expectedCheckIp: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+		},
+		{
+			desc:            "Bad remote addr",
+			remoteAddr:      "[127.0",
+			expectedCheckIp: "[127.0",
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			client := setupWithApiInspector(t,
+				func(r *Request) {
+					require.Equal(t, tc.expectedCheckIp, r.CheckIp)
+				})
+
+			sshEnv := sshenv.Env{RemoteAddr: tc.remoteAddr}
+			client.Verify(context.Background(), &commandargs.Shell{Env: sshEnv}, uploadPackAction, repo)
+		})
+	}
+}
+
 type testResponse struct {
 	body   []byte
 	status int
@@ -260,3 +271,29 @@
 
 	return client
 }
+
+func setupWithApiInspector(t *testing.T, inspector func(*Request)) *Client {
+	t.Helper()
+	requests := []testserver.TestRequestHandler{
+		{
+			Path: "/api/v4/internal/allowed",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				b, err := io.ReadAll(r.Body)
+				require.NoError(t, err)
+
+				var requestBody *Request
+				err = json.Unmarshal(b, &requestBody)
+				require.NoError(t, err)
+
+				inspector(requestBody)
+			},
+		},
+	}
+
+	url := testserver.StartSocketHttpServer(t, requests)
+
+	client, err := NewClient(&config.Config{GitlabUrl: url})
+	require.NoError(t, err)
+
+	return client
+}
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -33,10 +33,9 @@
 
 func NewGitalyCommand(cfg *config.Config, serviceName string, response *accessverifier.Response) *GitalyCommand {
 	gc := gitaly.Command{
-		ServiceName:     serviceName,
-		Address:         response.Gitaly.Address,
-		Token:           response.Gitaly.Token,
-		DialSidechannel: response.Gitaly.UseSidechannel,
+		ServiceName: serviceName,
+		Address:     response.Gitaly.Address,
+		Token:       response.Gitaly.Token,
 	}
 
 	return &GitalyCommand{Config: cfg, Response: response, Command: gc}
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -29,7 +29,7 @@
 
 func TestRunGitalyCommand(t *testing.T) {
 	cmd := NewGitalyCommand(
-		&config.Config{},
+		newConfig(),
 		string(commandargs.UploadPack),
 		&accessverifier.Response{
 			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
@@ -46,14 +46,12 @@
 
 func TestCachingOfGitalyConnections(t *testing.T) {
 	ctx := context.Background()
-	cfg := &config.Config{}
-	cfg.GitalyClient.InitSidechannelRegistry(ctx)
+	cfg := newConfig()
 	response := &accessverifier.Response{
 		Username: "user",
 		Gitaly: accessverifier.Gitaly{
-			Address:        "tcp://localhost:9999",
-			Token:          "token",
-			UseSidechannel: true,
+			Address: "tcp://localhost:9999",
+			Token:   "token",
 		},
 	}
 
@@ -71,7 +69,7 @@
 }
 
 func TestMissingGitalyAddress(t *testing.T) {
-	cmd := GitalyCommand{Config: &config.Config{}}
+	cmd := GitalyCommand{Config: newConfig()}
 
 	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, nil))
 	require.EqualError(t, err, "no gitaly_address given")
@@ -79,7 +77,7 @@
 
 func TestUnavailableGitalyErr(t *testing.T) {
 	cmd := NewGitalyCommand(
-		&config.Config{},
+		newConfig(),
 		string(commandargs.UploadPack),
 		&accessverifier.Response{
 			Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
@@ -101,7 +99,7 @@
 		{
 			name: "gitaly_feature_flags",
 			gc: NewGitalyCommand(
-				&config.Config{},
+				newConfig(),
 				string(commandargs.UploadPack),
 				&accessverifier.Response{
 					Gitaly: accessverifier.Gitaly{
@@ -209,3 +207,9 @@
 		})
 	}
 }
+
+func newConfig() *config.Config {
+	cfg := &config.Config{}
+	cfg.GitalyClient.InitSidechannelRegistry(context.Background())
+	return cfg
+}
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -22,6 +22,7 @@
 	sshdHitMaxSessionsName                    = "concurrent_limited_sessions_total"
 	sshdSessionDurationSecondsName            = "session_duration_seconds"
 	sshdSessionEstablishedDurationSecondsName = "session_established_duration_seconds"
+	sshdCanceledSessionsName                  = "canceled_sessions"
 
 	sliSshdSessionsTotalName       = "gitlab_sli:shell_sshd_sessions:total"
 	sliSshdSessionsErrorsTotalName = "gitlab_sli:shell_sshd_sessions:errors_total"
@@ -76,6 +77,15 @@
 		},
 	)
 
+	SshdCanceledSessions = promauto.NewCounter(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: sshdSubsystem,
+			Name:      sshdCanceledSessionsName,
+			Help:      "The number of canceled gitlab-sshd sessions.",
+		},
+	)
+
 	SliSshdSessionsTotal = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Name: sliSshdSessionsTotalName,
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -2,32 +2,54 @@
 
 import (
 	"context"
+	"time"
 
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
 
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
+const KeepAliveMsg = "keepalive@openssh.com"
+
+var EOFTimeout = 10 * time.Second
+
 type connection struct {
+	cfg                *config.Config
 	concurrentSessions *semaphore.Weighted
 	remoteAddr         string
+	sconn              *ssh.ServerConn
+	maxSessions        int64
 }
 
-type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request)
+type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request) error
 
-func newConnection(maxSessions int64, remoteAddr string) *connection {
+func newConnection(cfg *config.Config, remoteAddr string, sconn *ssh.ServerConn) *connection {
+	maxSessions := cfg.Server.ConcurrentSessionsLimit
+
 	return &connection{
+		cfg:                cfg,
+		maxSessions:        maxSessions,
 		concurrentSessions: semaphore.NewWeighted(maxSessions),
 		remoteAddr:         remoteAddr,
+		sconn:              sconn,
 	}
 }
 
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
+	if c.cfg.Server.ClientAliveIntervalSeconds > 0 {
+		ticker := time.NewTicker(c.cfg.Server.ClientAliveInterval())
+		defer ticker.Stop()
+		go c.sendKeepAliveMsg(ctx, ticker)
+	}
+
 	for newChannel := range chans {
 		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
@@ -49,6 +71,10 @@
 		}
 
 		go func() {
+			defer func(started time.Time) {
+				metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
+			}(time.Now())
+
 			defer c.concurrentSessions.Release(1)
 
 			// Prevent a panic in a single session from taking out the whole server
@@ -58,8 +84,40 @@
 				}
 			}()
 
-			handler(ctx, channel, requests)
+			metrics.SliSshdSessionsTotal.Inc()
+			err := handler(ctx, channel, requests)
+			if err != nil {
+				if grpcstatus.Convert(err).Code() == grpccodes.Canceled {
+					metrics.SshdCanceledSessions.Inc()
+				} else {
+					metrics.SliSshdSessionsErrorsTotal.Inc()
+				}
+			}
+
 			ctxlog.Info("connection: handle: done")
 		}()
 	}
+
+	// When a connection has been prematurely closed we block execution until all concurrent sessions are released
+	// in order to allow Gitaly complete the operations and close all the channels gracefully.
+	// If it didn't happen within timeout, we unblock the execution
+	// Related issue: https://gitlab.com/gitlab-org/gitlab-shell/-/issues/563
+	ctx, cancel := context.WithTimeout(ctx, EOFTimeout)
+	defer cancel()
+	c.concurrentSessions.Acquire(ctx, c.maxSessions)
 }
+
+func (c *connection) sendKeepAliveMsg(ctx context.Context, ticker *time.Ticker) {
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-ticker.C:
+			ctxlog.Debug("session: handleShell: send keepalive message to a client")
+
+			c.sconn.SendRequest(KeepAliveMsg, true, nil)
+		}
+	}
+}
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -3,10 +3,18 @@
 import (
 	"context"
 	"errors"
+	"sync"
 	"testing"
+	"time"
 
+	"github.com/prometheus/client_golang/prometheus/testutil"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
+
+	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
 
 type rejectCall struct {
@@ -47,8 +55,32 @@
 	return f.extraData
 }
 
+type fakeConn struct {
+	ssh.Conn
+
+	sentRequestName string
+	mu              sync.Mutex
+}
+
+func (f *fakeConn) SentRequestName() string {
+	f.mu.Lock()
+	defer f.mu.Unlock()
+
+	return f.sentRequestName
+}
+
+func (f *fakeConn) SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error) {
+	f.mu.Lock()
+	defer f.mu.Unlock()
+
+	f.sentRequestName = name
+
+	return true, nil, nil
+}
+
 func setup(sessionsNum int64, newChannel *fakeNewChannel) (*connection, chan ssh.NewChannel) {
-	conn := newConnection(sessionsNum, "127.0.0.1:50000")
+	cfg := &config.Config{Server: config.ServerConfig{ConcurrentSessionsLimit: sessionsNum, ClientAliveIntervalSeconds: 1}}
+	conn := newConnection(cfg, "127.0.0.1:50000", &ssh.ServerConn{&fakeConn{}, nil})
 
 	chans := make(chan ssh.NewChannel, 1)
 	chans <- newChannel
@@ -62,7 +94,7 @@
 
 	numSessions := 0
 	require.NotPanics(t, func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 			numSessions += 1
 			close(chans)
 			panic("This is a panic")
@@ -100,8 +132,9 @@
 	defer cancel()
 
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 			<-ctx.Done() // Keep the accepted channel open until the end of the test
+			return nil
 		})
 	}()
 
@@ -114,9 +147,10 @@
 	conn, chans := setup(1, newChannel)
 
 	channelHandled := false
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 		channelHandled = true
 		close(chans)
+		return nil
 	})
 
 	require.True(t, channelHandled)
@@ -132,8 +166,9 @@
 
 	channelHandled := false
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) {
+		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 			channelHandled = true
+			return nil
 		})
 	}()
 
@@ -145,3 +180,46 @@
 
 	require.False(t, channelHandled)
 }
+
+func TestClientAliveInterval(t *testing.T) {
+	f := &fakeConn{}
+
+	conn := newConnection(&config.Config{}, "127.0.0.1:50000", &ssh.ServerConn{f, nil})
+
+	ticker := time.NewTicker(time.Millisecond)
+	defer ticker.Stop()
+
+	go conn.sendKeepAliveMsg(context.Background(), ticker)
+
+	require.Eventually(t, func() bool { return KeepAliveMsg == f.SentRequestName() }, time.Second, time.Millisecond)
+}
+
+func TestSessionsMetrics(t *testing.T) {
+	// Unfortunately, there is no working way to reset Counter (not CounterVec)
+	// https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#pkg-index
+	initialSessionsTotal := testutil.ToFloat64(metrics.SliSshdSessionsTotal)
+	initialSessionsErrorTotal := testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal)
+	initialCanceledSessions := testutil.ToFloat64(metrics.SshdCanceledSessions)
+
+	newChannel := &fakeNewChannel{channelType: "session"}
+
+	conn, chans := setup(1, newChannel)
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		close(chans)
+		return errors.New("custom error")
+	})
+
+	require.InDelta(t, initialSessionsTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+	require.InDelta(t, initialCanceledSessions, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
+
+	conn, chans = setup(1, newChannel)
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		close(chans)
+		return grpcstatus.Error(grpccodes.Canceled, "error")
+	})
+
+	require.InDelta(t, initialSessionsTotal+2, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
+}
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -22,7 +22,6 @@
 	channel     ssh.Channel
 	gitlabKeyId string
 	remoteAddr  string
-	success     bool
 
 	// State managed by the session
 	execCmd            string
@@ -42,11 +41,12 @@
 	ExitStatus uint32
 }
 
-func (s *session) handle(ctx context.Context, requests <-chan *ssh.Request) {
+func (s *session) handle(ctx context.Context, requests <-chan *ssh.Request) error {
 	ctxlog := log.ContextLogger(ctx)
 
 	ctxlog.Debug("session: handle: entering request loop")
 
+	var err error
 	for req := range requests {
 		sessionLog := ctxlog.WithFields(log.Fields{
 			"bytesize":   len(req.Payload),
@@ -58,12 +58,14 @@
 		var shouldContinue bool
 		switch req.Type {
 		case "env":
-			shouldContinue = s.handleEnv(ctx, req)
+			shouldContinue, err = s.handleEnv(ctx, req)
 		case "exec":
-			shouldContinue = s.handleExec(ctx, req)
+			shouldContinue, err = s.handleExec(ctx, req)
 		case "shell":
 			shouldContinue = false
-			s.exit(ctx, s.handleShell(ctx, req))
+			var status uint32
+			status, err = s.handleShell(ctx, req)
+			s.exit(ctx, status)
 		default:
 			// Ignore unknown requests but don't terminate the session
 			shouldContinue = true
@@ -84,15 +86,17 @@
 	}
 
 	ctxlog.Debug("session: handle: exiting request loop")
+
+	return err
 }
 
-func (s *session) handleEnv(ctx context.Context, req *ssh.Request) bool {
+func (s *session) handleEnv(ctx context.Context, req *ssh.Request) (bool, error) {
 	var accepted bool
 	var envRequest envRequest
 
 	if err := ssh.Unmarshal(req.Payload, &envRequest); err != nil {
 		log.ContextLogger(ctx).WithError(err).Error("session: handleEnv: failed to unmarshal request")
-		return false
+		return false, err
 	}
 
 	switch envRequest.Name {
@@ -113,23 +117,24 @@
 		ctx, log.Fields{"accepted": accepted, "env_request": envRequest},
 	).Debug("session: handleEnv: processed")
 
-	return true
+	return true, nil
 }
 
-func (s *session) handleExec(ctx context.Context, req *ssh.Request) bool {
+func (s *session) handleExec(ctx context.Context, req *ssh.Request) (bool, error) {
 	var execRequest execRequest
 	if err := ssh.Unmarshal(req.Payload, &execRequest); err != nil {
-		return false
+		return false, err
 	}
 
 	s.execCmd = execRequest.Command
 
-	s.exit(ctx, s.handleShell(ctx, req))
+	status, err := s.handleShell(ctx, req)
+	s.exit(ctx, status)
 
-	return false
+	return false, err
 }
 
-func (s *session) handleShell(ctx context.Context, req *ssh.Request) uint32 {
+func (s *session) handleShell(ctx context.Context, req *ssh.Request) (uint32, error) {
 	ctxlog := log.ContextLogger(ctx)
 
 	if req.WantReply {
@@ -157,7 +162,7 @@
 			s.toStderr(ctx, "Failed to parse command: %v\n", err.Error())
 		}
 		s.toStderr(ctx, "Unknown command: %v\n", s.execCmd)
-		return 128
+		return 128, err
 	}
 
 	cmdName := reflect.TypeOf(cmd).String()
@@ -165,12 +170,12 @@
 
 	if err := cmd.Execute(ctx); err != nil {
 		s.toStderr(ctx, "remote: ERROR: %v\n", err.Error())
-		return 1
+		return 1, err
 	}
 
 	ctxlog.Info("session: handleShell: command executed successfully")
 
-	return 0
+	return 0, nil
 }
 
 func (s *session) toStderr(ctx context.Context, format string, args ...interface{}) {
@@ -183,8 +188,6 @@
 	log.WithContextFields(ctx, log.Fields{"exit_status": status}).Info("session: exit: exiting")
 	req := exitStatusReq{ExitStatus: status}
 
-	s.success = status == 0
-
 	s.channel.CloseWrite()
 	s.channel.SendRequest("exit-status", false, ssh.Marshal(req))
 }
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -3,6 +3,7 @@
 import (
 	"bytes"
 	"context"
+	"errors"
 	"io"
 	"net/http"
 	"testing"
@@ -60,22 +61,26 @@
 	testCases := []struct {
 		desc                    string
 		payload                 []byte
+		expectedErr             error
 		expectedProtocolVersion string
 		expectedResult          bool
 	}{
 		{
 			desc:                    "invalid payload",
 			payload:                 []byte("invalid"),
+			expectedErr:             errors.New("ssh: unmarshal error for field Name of type envRequest"),
 			expectedProtocolVersion: "1",
 			expectedResult:          false,
 		}, {
 			desc:                    "valid payload",
 			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL", Value: "2"}),
+			expectedErr:             nil,
 			expectedProtocolVersion: "2",
 			expectedResult:          true,
 		}, {
 			desc:                    "valid payload with forbidden env var",
 			payload:                 ssh.Marshal(envRequest{Name: "GIT_PROTOCOL_ENV", Value: "2"}),
+			expectedErr:             nil,
 			expectedProtocolVersion: "1",
 			expectedResult:          true,
 		},
@@ -86,8 +91,11 @@
 			s := &session{gitProtocolVersion: "1"}
 			r := &ssh.Request{Payload: tc.payload}
 
-			require.Equal(t, s.handleEnv(context.Background(), r), tc.expectedResult)
-			require.Equal(t, s.gitProtocolVersion, tc.expectedProtocolVersion)
+			shouldContinue, err := s.handleEnv(context.Background(), r)
+
+			require.Equal(t, tc.expectedErr, err)
+			require.Equal(t, tc.expectedResult, shouldContinue)
+			require.Equal(t, tc.expectedProtocolVersion, s.gitProtocolVersion)
 		})
 	}
 }
@@ -96,23 +104,24 @@
 	testCases := []struct {
 		desc               string
 		payload            []byte
+		expectedErr        error
 		expectedExecCmd    string
 		sentRequestName    string
 		sentRequestPayload []byte
-		success            bool
 	}{
 		{
 			desc:            "invalid payload",
 			payload:         []byte("invalid"),
+			expectedErr:     errors.New("ssh: unmarshal error for field Command of type execRequest"),
 			expectedExecCmd: "",
 			sentRequestName: "",
 		}, {
 			desc:               "valid payload",
 			payload:            ssh.Marshal(execRequest{Command: "discover"}),
+			expectedErr:        nil,
 			expectedExecCmd:    "discover",
 			sentRequestName:    "exit-status",
 			sentRequestPayload: ssh.Marshal(exitStatusReq{ExitStatus: 0}),
-			success:            true,
 		},
 	}
 
@@ -129,47 +138,53 @@
 			}
 			r := &ssh.Request{Payload: tc.payload}
 
-			require.Equal(t, false, s.handleExec(context.Background(), r))
+			shouldContinue, err := s.handleExec(context.Background(), r)
+
+			require.Equal(t, tc.expectedErr, err)
+			require.Equal(t, false, shouldContinue)
 			require.Equal(t, tc.sentRequestName, f.sentRequestName)
 			require.Equal(t, tc.sentRequestPayload, f.sentRequestPayload)
-			require.Equal(t, tc.success, s.success)
 		})
 	}
 }
 
 func TestHandleShell(t *testing.T) {
 	testCases := []struct {
-		desc             string
-		cmd              string
-		errMsg           string
-		gitlabKeyId      string
-		expectedExitCode uint32
-		success          bool
+		desc              string
+		cmd               string
+		errMsg            string
+		gitlabKeyId       string
+		expectedErrString string
+		expectedExitCode  uint32
 	}{
 		{
-			desc:             "fails to parse command",
-			cmd:              `\`,
-			errMsg:           "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
-			gitlabKeyId:      "root",
-			expectedExitCode: 128,
+			desc:              "fails to parse command",
+			cmd:               `\`,
+			errMsg:            "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
+			gitlabKeyId:       "root",
+			expectedErrString: "Invalid SSH command: invalid command line string",
+			expectedExitCode:  128,
 		}, {
-			desc:             "specified command is unknown",
-			cmd:              "unknown-command",
-			errMsg:           "Unknown command: unknown-command\n",
-			gitlabKeyId:      "root",
-			expectedExitCode: 128,
+			desc:              "specified command is unknown",
+			cmd:               "unknown-command",
+			errMsg:            "Unknown command: unknown-command\n",
+			gitlabKeyId:       "root",
+			expectedErrString: "Disallowed command",
+			expectedExitCode:  128,
 		}, {
-			desc:             "fails to parse command",
-			cmd:              "discover",
-			gitlabKeyId:      "",
-			errMsg:           "remote: ERROR: Failed to get username: who='' is invalid\n",
-			expectedExitCode: 1,
+			desc:              "fails to parse command",
+			cmd:               "discover",
+			gitlabKeyId:       "",
+			errMsg:            "remote: ERROR: Failed to get username: who='' is invalid\n",
+			expectedErrString: "Failed to get username: who='' is invalid",
+			expectedExitCode:  1,
 		}, {
-			desc:             "fails to parse command",
-			cmd:              "discover",
-			errMsg:           "",
-			gitlabKeyId:      "root",
-			expectedExitCode: 0,
+			desc:              "fails to parse command",
+			cmd:               "discover",
+			errMsg:            "",
+			gitlabKeyId:       "root",
+			expectedErrString: "",
+			expectedExitCode:  0,
 		},
 	}
 
@@ -186,7 +201,13 @@
 			}
 			r := &ssh.Request{}
 
-			require.Equal(t, tc.expectedExitCode, s.handleShell(context.Background(), r))
+			exitCode, err := s.handleShell(context.Background(), r)
+
+			if tc.expectedErrString != "" {
+				require.Equal(t, tc.expectedErrString, err.Error())
+			}
+
+			require.Equal(t, tc.expectedExitCode, exitCode)
 			require.Equal(t, tc.errMsg, out.String())
 		})
 	}
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -5,6 +5,7 @@
 	"fmt"
 	"net"
 	"net/http"
+	"strings"
 	"sync"
 	"time"
 
@@ -32,7 +33,7 @@
 	Config *config.Config
 
 	status       status
-	statusMu     sync.Mutex
+	statusMu     sync.RWMutex
 	wg           sync.WaitGroup
 	listener     net.Listener
 	serverConfig *serverConfig
@@ -95,7 +96,7 @@
 	if s.Config.Server.ProxyProtocol {
 		sshListener = &proxyproto.Listener{
 			Listener:          sshListener,
-			Policy:            unconditionalRequirePolicy,
+			Policy:            s.requirePolicy,
 			ReadHeaderTimeout: ProxyHeaderTimeout,
 		}
 
@@ -139,26 +140,15 @@
 }
 
 func (s *Server) getStatus() status {
-	s.statusMu.Lock()
-	defer s.statusMu.Unlock()
+	s.statusMu.RLock()
+	defer s.statusMu.RUnlock()
 
 	return s.status
 }
 
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
-	success := false
-
 	metrics.SshdConnectionsInFlight.Inc()
-	started := time.Now()
-	defer func() {
-		metrics.SshdConnectionsInFlight.Dec()
-		metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
-
-		metrics.SliSshdSessionsTotal.Inc()
-		if !success {
-			metrics.SliSshdSessionsErrorsTotal.Inc()
-		}
-	}()
+	defer metrics.SshdConnectionsInFlight.Dec()
 
 	remoteAddr := nconn.RemoteAddr().String()
 
@@ -174,22 +164,24 @@
 	defer func() {
 		if err := recover(); err != nil {
 			ctxlog.Warn("panic handling session")
+
+			metrics.SliSshdSessionsErrorsTotal.Inc()
 		}
 	}()
 
 	ctxlog.Info("server: handleConn: start")
 
-	sconn, chans, reqs, err := s.initSSHConnection(ctx, nconn)
+	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
-		ctxlog.WithError(err).Error("server: handleConn: failed to initialize SSH connection")
+		ctxlog.WithError(err).Warn("server: handleConn: failed to initialize SSH connection")
 		return
 	}
-
 	go ssh.DiscardRequests(reqs)
 
+	started := time.Now()
 	var establishSessionDuration float64
-	conn := newConnection(s.Config.Server.ConcurrentSessionsLimit, remoteAddr)
-	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) {
+	conn := newConnection(s.Config, remoteAddr, sconn)
+	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) error {
 		establishSessionDuration = time.Since(started).Seconds()
 		metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
 
@@ -200,31 +192,28 @@
 			remoteAddr:  remoteAddr,
 		}
 
-		session.handle(ctx, requests)
-
-		success = session.success
+		return session.handle(ctx, requests)
 	})
 
+	reason := sconn.Wait()
 	ctxlog.WithFields(log.Fields{
 		"duration_s":                   time.Since(started).Seconds(),
 		"establish_session_duration_s": establishSessionDuration,
+		"reason":                       reason,
 	}).Info("server: handleConn: done")
 }
 
-func (s *Server) initSSHConnection(ctx context.Context, nconn net.Conn) (sconn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request, err error) {
-	timer := time.AfterFunc(s.Config.Server.LoginGraceTime(), func() {
-		nconn.Close()
-	})
-	defer func() {
-		// If time.Stop() equals false, that means that AfterFunc has been executed
-		if !timer.Stop() {
-			err = fmt.Errorf("initSSHConnection: ssh handshake timeout of %v", s.Config.Server.LoginGraceTime())
-		}
-	}()
-
-	return ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
+func (s *Server) requirePolicy(_ net.Addr) (proxyproto.Policy, error) {
+	// Set the Policy value based on config
+	// Values are taken from https://github.com/pires/go-proxyproto/blob/195fedcfbfc1be163f3a0d507fac1709e9d81fed/policy.go#L20
+	switch strings.ToLower(s.Config.Server.ProxyPolicy) {
+	case "require":
+		return proxyproto.REQUIRE, nil
+	case "ignore":
+		return proxyproto.IGNORE, nil
+	case "reject":
+		return proxyproto.REJECT, nil
+	default:
+		return proxyproto.USE, nil
+	}
 }
-
-func unconditionalRequirePolicy(_ net.Addr) (proxyproto.Policy, error) {
-	return proxyproto.REQUIRE, nil
-}
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -11,6 +11,7 @@
 	"testing"
 	"time"
 
+	"github.com/pires/go-proxyproto"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
@@ -49,15 +50,101 @@
 }
 
 func TestListenAndServeRejectsPlainConnectionsWhenProxyProtocolEnabled(t *testing.T) {
-	setupServerWithProxyProtocolEnabled(t)
+	target, err := net.ResolveTCPAddr("tcp", serverUrl)
+	require.NoError(t, err)
 
-	client, err := ssh.Dial("tcp", serverUrl, clientConfig(t))
-	if client != nil {
-		client.Close()
+	header := &proxyproto.Header{
+		Version:           2,
+		Command:           proxyproto.PROXY,
+		TransportProtocol: proxyproto.TCPv4,
+		SourceAddr: &net.TCPAddr{
+			IP:   net.ParseIP("10.1.1.1"),
+			Port: 1000,
+		},
+		DestinationAddr: target,
 	}
 
-	require.Error(t, err, "Expected plain SSH request to be failed")
-	require.Regexp(t, "ssh: handshake failed", err.Error())
+	testCases := []struct {
+		desc        string
+		proxyPolicy string
+		header      *proxyproto.Header
+		isRejected  bool
+	}{
+		{
+			desc:        "USE (default) without a header",
+			proxyPolicy: "",
+			header:      nil,
+			isRejected:  false,
+		},
+		{
+			desc:        "USE (default) with a header",
+			proxyPolicy: "",
+			header:      header,
+			isRejected:  false,
+		},
+		{
+			desc:        "REQUIRE without a header",
+			proxyPolicy: "require",
+			header:      nil,
+			isRejected:  true,
+		},
+		{
+			desc:        "REQUIRE with a header",
+			proxyPolicy: "require",
+			header:      header,
+			isRejected:  false,
+		},
+		{
+			desc:        "REJECT without a header",
+			proxyPolicy: "reject",
+			header:      nil,
+			isRejected:  false,
+		},
+		{
+			desc:        "REJECT with a header",
+			proxyPolicy: "reject",
+			header:      header,
+			isRejected:  true,
+		},
+		{
+			desc:        "IGNORE without a header",
+			proxyPolicy: "ignore",
+			header:      nil,
+			isRejected:  false,
+		},
+		{
+			desc:        "IGNORE with a header",
+			proxyPolicy: "ignore",
+			header:      header,
+			isRejected:  false,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			setupServerWithConfig(t, &config.Config{Server: config.ServerConfig{ProxyProtocol: true, ProxyPolicy: tc.proxyPolicy}})
+
+			conn, err := net.DialTCP("tcp", nil, target)
+			require.NoError(t, err)
+
+			if tc.header != nil {
+				_, err := header.WriteTo(conn)
+				require.NoError(t, err)
+			}
+
+			sshConn, _, _, err := ssh.NewClientConn(conn, serverUrl, clientConfig(t))
+			if sshConn != nil {
+				sshConn.Close()
+			}
+
+			if tc.isRejected {
+				require.Error(t, err, "Expected plain SSH request to be failed")
+				require.Regexp(t, "ssh: handshake failed", err.Error())
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
 }
 
 func TestCorrelationId(t *testing.T) {
@@ -135,45 +222,12 @@
 	require.Nil(t, s.Shutdown())
 }
 
-func TestLoginGraceTime(t *testing.T) {
-	s := setupServer(t)
-
-	unauthenticatedRequestStatus := make(chan string)
-	completed := make(chan bool)
-
-	clientCfg := clientConfig(t)
-	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
-		unauthenticatedRequestStatus <- "authentication-started"
-		<-completed // Wait infinitely
-
-		return nil
-	}
-
-	go func() {
-		// Start an SSH connection that never ends
-		ssh.Dial("tcp", serverUrl, clientCfg)
-	}()
-
-	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
-
-	// Verify that the server shutdowns instantly without waiting for any connection to execute
-	// That means that the server doesn't have any connections to wait for
-	require.NoError(t, s.Shutdown())
-	verifyStatus(t, s, StatusClosed)
-}
-
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
 	return setupServerWithConfig(t, nil)
 }
 
-func setupServerWithProxyProtocolEnabled(t *testing.T) *Server {
-	t.Helper()
-
-	return setupServerWithConfig(t, &config.Config{Server: config.ServerConfig{ProxyProtocol: true}})
-}
-
 func setupServerWithConfig(t *testing.T, cfg *config.Config) *Server {
 	t.Helper()
 
@@ -211,7 +265,7 @@
 	cfg.User = user
 	cfg.Server.Listen = serverUrl
 	cfg.Server.ConcurrentSessionsLimit = 1
-	cfg.Server.LoginGraceTimeSeconds = 1
+	cfg.Server.ClientAliveIntervalSeconds = 15
 	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
 
 	s, err := NewServer(cfg)
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -81,14 +81,6 @@
 }
 
 func BuildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
-	return buildAllowedWithGitalyHandlers(t, gitalyAddress, false)
-}
-
-func BuildAllowedWithGitalyHandlersWithSidechannel(t *testing.T, gitalyAddress string) []testserver.TestRequestHandler {
-	return buildAllowedWithGitalyHandlers(t, gitalyAddress, true)
-}
-
-func buildAllowedWithGitalyHandlers(t *testing.T, gitalyAddress string, useSidechannel bool) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/allowed",
@@ -116,9 +108,6 @@
 						},
 					},
 				}
-				if useSidechannel {
-					body["gitaly"].(map[string]interface{})["use_sidechannel"] = true
-				}
 				require.NoError(t, json.NewEncoder(w).Encode(body))
 			},
 		},
diff --git a/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json b/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
deleted file mode 100644
--- a/internal/testhelper/testdata/testroot/responses/allowed_sidechannel.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-	"status": true,
-	"gl_repository": "project-26",
-	"gl_project_path": "group/private",
-	"gl_id": "user-1",
-	"gl_username": "root",
-	"git_config_options": ["option"],
-	"gitaly": {
-		"repository": {
-			"storage_name": "default",
-			"relative_path": "@hashed/5f/9c/5f9c4ab08cac7457e9111a30e4664920607ea2c115a1433d7be98e97e64244ca.git",
-			"git_object_directory": "path/to/git_object_directory",
-			"git_alternate_object_directories": ["path/to/git_alternate_object_directory"],
-			"gl_repository": "project-26",
-			"gl_project_path": "group/private"
-		},
-		"address": "unix:gitaly.socket",
-		"token": "token",
-               "use_sidechannel": true
-	},
-	"git_protocol": "protocol",
-	"gl_console_messages": ["console", "message"]
-}
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1663967415 -7200
#      Fri Sep 23 23:10:15 2022 +0200
# Branch heptapod
# Node ID a0e3a223b1cf8987f61c0d293652171787290986
# Parent  7b25801e4afc9c3d6e7016fd220cce310db6bd1d
# Parent  0ea5c918a3bf8342b43bb8ec5e616bec564f41f4
Merged upstream v14.3.1 into Heptapod Shell

diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,4 @@
 tags
 tmp/*
 vendor
+.DS_Store
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,7 +8,7 @@
       - '/ci/danger-review.yml'
 
 variables:
-  DOCKER_VERSION: "20.10.3"
+  DOCKER_VERSION: "20.10.15"
 
 workflow:
   rules: &workflow_rules
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.3.1
+
+- Exclude API errors from error rate !630
+
 v14.3.0
 
 - Remove deprecated bundler-audit !626
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.3.0
+14.3.1
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -37,6 +37,14 @@
 	userAgent  string
 }
 
+type ApiError struct {
+	Msg string
+}
+
+func (e *ApiError) Error() string {
+	return e.Msg
+}
+
 func NewGitlabNetClient(
 	user,
 	password,
@@ -101,9 +109,9 @@
 	parsedResponse := &ErrorResponse{}
 
 	if err := json.NewDecoder(resp.Body).Decode(parsedResponse); err != nil {
-		return fmt.Errorf("Internal API error (%v)", resp.StatusCode)
+		return &ApiError{fmt.Sprintf("Internal API error (%v)", resp.StatusCode)}
 	} else {
-		return fmt.Errorf(parsedResponse.Message)
+		return &ApiError{parsedResponse.Message}
 	}
 
 }
@@ -157,7 +165,7 @@
 
 	if err != nil {
 		logger.WithError(err).Error("Internal API unreachable")
-		return nil, fmt.Errorf("Internal API unreachable")
+		return nil, &ApiError{"Internal API unreachable"}
 	}
 
 	if response != nil {
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -2,6 +2,7 @@
 
 import (
 	"context"
+	"errors"
 	"time"
 
 	"golang.org/x/crypto/ssh"
@@ -9,6 +10,7 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
+	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
@@ -90,7 +92,10 @@
 				if grpcstatus.Convert(err).Code() == grpccodes.Canceled {
 					metrics.SshdCanceledSessions.Inc()
 				} else {
-					metrics.SliSshdSessionsErrorsTotal.Inc()
+					var apiError *client.ApiError
+					if !errors.As(err, &apiError) {
+						metrics.SliSshdSessionsErrorsTotal.Inc()
+					}
 				}
 			}
 
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -13,6 +13,7 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
+	"gitlab.com/gitlab-org/gitlab-shell/client"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
@@ -222,4 +223,14 @@
 	require.InDelta(t, initialSessionsTotal+2, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
+
+	conn, chans = setup(1, newChannel)
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		close(chans)
+		return &client.ApiError{"api error"}
+	})
+
+	require.InDelta(t, initialSessionsTotal+3, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
 }
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1663967468 -7200
#      Fri Sep 23 23:11:08 2022 +0200
# Branch heptapod
# Node ID 61f29699614da28ec795cec912a75f398df84af3
# Parent  a0e3a223b1cf8987f61c0d293652171787290986
Updated Heptapod version files

diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 14.3.2
+
+- Bump to upstream GitLab Shell v14.3.1
+
 ## 14.3.1
 
 - Filled in `HEPTAPOD_VERSION` (had been forgotten in 14.0 through
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-14.3.1
+14.3.2
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1663967912 -7200
#      Fri Sep 23 23:18:32 2022 +0200
# Branch heptapod
# Node ID c0d78aade4f306a4f575ab4a56d488428690f485
# Parent  61f29699614da28ec795cec912a75f398df84af3
Added tag heptapod-14.3.2 for changeset 61f29699614d

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -44,3 +44,4 @@
 98d781e90b1bb472b9c37ad5fbdbed541e1457c5 heptapod-14.2.0
 7e62e4be19def9c1f1773e326480a9429210bb8d heptapod-14.3.0
 fca0cc645e5bbe898f18befe0f31f51f091db81b heptapod-14.3.1
+61f29699614da28ec795cec912a75f398df84af3 heptapod-14.3.2
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1664039691 -7200
#      Sat Sep 24 19:14:51 2022 +0200
# Branch heptapod
# Node ID 8696661a67a6e7816fca8066dcbdc66b261feb5b
# Parent  c0d78aade4f306a4f575ab4a56d488428690f485
# Parent  b5a0002e1a3887d92469d212c04d461bda32c6ef
Merged upstream GitLab Shell v14.4.0 into Heptapod Shell

and filled-in Heptapod version files

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+v14.4.0
+
+- Allow configuring SSH server algorithms !633
+- Update gitlab-org/golang-crypto module version !632
+
 v14.3.1
 
 - Exclude API errors from error rate !630
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 14.4.0
+
+- Bump to upstream GitLab Shell v14.4.0
+
 ## 14.3.2
 
 - Bump to upstream GitLab Shell v14.3.1
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-14.3.2
+14.4.0
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.3.1
+14.4.0
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -100,6 +100,12 @@
   readiness_probe: "/start"
   # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
   liveness_probe: "/health"
+  # Specifies the available message authentication code algorithms that are used for protecting data integrity
+  macs: [hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1]
+  # Specifies the available Key Exchange algorithms
+  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1]
+  # Specified the ciphers allowed
+  ciphers: [aes128-gcm@openssh.com, chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-ctr, aes192-ctr,aes256-ctr]
   # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -81,4 +81,4 @@
 	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
 )
 
-replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80
+replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -888,8 +888,8 @@
 gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
-gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80 h1:rVzTLlEDlX/ocWqNxeuxb9XklLzmI9FdP0teRqkSg00=
-gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac h1:qNUzqBTbEGGjF5Fp0NWz3rNmqamwchxM+QKUZYeOS1c=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -38,6 +38,9 @@
 	ReadinessProbe             string   `yaml:"readiness_probe"`
 	LivenessProbe              string   `yaml:"liveness_probe"`
 	HostKeyFiles               []string `yaml:"host_key_files,omitempty"`
+	MACs                       []string `yaml:"macs"`
+	KexAlgorithms              []string `yaml:"kex_algorithms"`
+	Ciphers                    []string `yaml:"ciphers"`
 }
 
 type HttpSettingsConfig struct {
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -16,6 +16,14 @@
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
+var supportedMACs = []string{
+	"hmac-sha2-256-etm@openssh.com",
+	"hmac-sha2-512-etm@openssh.com",
+	"hmac-sha2-256",
+	"hmac-sha2-512",
+	"hmac-sha1",
+}
+
 type serverConfig struct {
 	cfg                  *config.Config
 	hostKeys             []ssh.Signer
@@ -86,6 +94,20 @@
 		},
 	}
 
+	if len(s.cfg.Server.MACs) > 0 {
+		sshCfg.MACs = s.cfg.Server.MACs
+	} else {
+		sshCfg.MACs = supportedMACs
+	}
+
+	if len(s.cfg.Server.KexAlgorithms) > 0 {
+		sshCfg.KeyExchanges = s.cfg.Server.KexAlgorithms
+	}
+
+	if len(s.cfg.Server.Ciphers) > 0 {
+		sshCfg.Ciphers = s.cfg.Server.Ciphers
+	}
+
 	for _, key := range s.hostKeys {
 		sshCfg.AddHostKey(key)
 	}
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
--- a/internal/sshd/server_config_test.go
+++ b/internal/sshd/server_config_test.go
@@ -80,6 +80,67 @@
 	}
 }
 
+func TestDefaultAlgorithms(t *testing.T) {
+	srvCfg := &serverConfig{cfg: &config.Config{}}
+	sshServerConfig := srvCfg.get(context.Background())
+
+	require.Equal(t, supportedMACs, sshServerConfig.MACs)
+	require.Nil(t, sshServerConfig.KeyExchanges)
+	require.Nil(t, sshServerConfig.Ciphers)
+
+	sshServerConfig.SetDefaults()
+
+	require.Equal(t, supportedMACs, sshServerConfig.MACs)
+
+	defaultKeyExchanges := []string{
+		"curve25519-sha256",
+		"curve25519-sha256@libssh.org",
+		"ecdh-sha2-nistp256",
+		"ecdh-sha2-nistp384",
+		"ecdh-sha2-nistp521",
+		"diffie-hellman-group14-sha256",
+		"diffie-hellman-group14-sha1",
+	}
+	require.Equal(t, defaultKeyExchanges, sshServerConfig.KeyExchanges)
+
+	defaultCiphers := []string{
+		"aes128-gcm@openssh.com",
+		"chacha20-poly1305@openssh.com",
+		"aes256-gcm@openssh.com",
+		"aes128-ctr",
+		"aes192-ctr",
+		"aes256-ctr",
+	}
+	require.Equal(t, defaultCiphers, sshServerConfig.Ciphers)
+}
+
+func TestCustomAlgorithms(t *testing.T) {
+	customMACs := []string{"hmac-sha2-512-etm@openssh.com"}
+	customKexAlgos := []string{"curve25519-sha256"}
+	customCiphers := []string{"aes256-gcm@openssh.com"}
+
+	srvCfg := &serverConfig{
+		cfg: &config.Config{
+			Server: config.ServerConfig{
+				MACs:          customMACs,
+				KexAlgorithms: customKexAlgos,
+				Ciphers:       customCiphers,
+			},
+		},
+	}
+	sshServerConfig := srvCfg.get(context.Background())
+
+	require.Equal(t, customMACs, sshServerConfig.MACs)
+	require.Equal(t, customKexAlgos, sshServerConfig.KeyExchanges)
+	require.Equal(t, customCiphers, sshServerConfig.Ciphers)
+
+	sshServerConfig.SetDefaults()
+
+	require.Equal(t, customMACs, sshServerConfig.MACs)
+	require.Equal(t, customKexAlgos, sshServerConfig.KeyExchanges)
+	require.Equal(t, customCiphers, sshServerConfig.Ciphers)
+}
+
 func rsaPublicKey(t *testing.T) ssh.PublicKey {
 	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
 	require.NoError(t, err)
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1664040146 -7200
#      Sat Sep 24 19:22:26 2022 +0200
# Branch heptapod
# Node ID e8040b2b35ce6f600ac4d9bb0ffd03e5444f3402
# Parent  8696661a67a6e7816fca8066dcbdc66b261feb5b
Added tag heptapod-14.4.0 for changeset 8696661a67a6

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -45,3 +45,4 @@
 7e62e4be19def9c1f1773e326480a9429210bb8d heptapod-14.3.0
 fca0cc645e5bbe898f18befe0f31f51f091db81b heptapod-14.3.1
 61f29699614da28ec795cec912a75f398df84af3 heptapod-14.3.2
+8696661a67a6e7816fca8066dcbdc66b261feb5b heptapod-14.4.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1664446728 -7200
#      Thu Sep 29 12:18:48 2022 +0200
# Branch heptapod
# Node ID aaebb02398004cb60c56d98824084bf9a2c17073
# Parent  e8040b2b35ce6f600ac4d9bb0ffd03e5444f3402
# Parent  c1e06d31b6ef53cea0f58afa656b99c22fd38743
Merged upstream GitLab Shell v14.5.0

and updated Heptapod version files accordingly

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.5.0
+
+- Make ProxyHeaderTimeout configurable !635
+
 v14.4.0
 
 - Allow configuring SSH server algorithms !633
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -14,6 +14,10 @@
 
 - Bump to upstream GitLab Shell v14.4.0
 
+## 14.5.0
+
+- Bump to upstream GitLab Shell v14.5.0
+
 ## 14.3.2
 
 - Bump to upstream GitLab Shell v14.3.1
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-14.4.0
+14.5.0
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.4.0
+14.5.0
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -99,11 +99,12 @@
 		sig := <-done
 		signal.Reset(syscall.SIGINT, syscall.SIGTERM)
 
-		log.WithContextFields(ctx, log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
+		gracePeriod := time.Duration(cfg.Server.GracePeriod)
+		log.WithContextFields(ctx, log.Fields{"shutdown_timeout_s": gracePeriod.Seconds(), "signal": sig.String()}).Info("Shutdown initiated")
 
 		server.Shutdown()
 
-		<-time.After(cfg.Server.GracePeriod())
+		<-time.After(gracePeriod)
 
 		cancel()
 
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -96,6 +96,8 @@
   client_alive_interval: 15
   # The server waits for this time (in seconds) for the ongoing connections to complete before shutting down. Defaults to 10.
   grace_period: 10
+  # A short timeout to decide to abort the connection if the protocol header is not seen within it. Defaults to 500ms
+  proxy_header_timeout: 500ms
   # The endpoint that returns 200 OK if the server is ready to receive incoming connections; otherwise, it returns 503 Service Unavailable. Defaults to "/start".
   readiness_probe: "/start"
   # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -27,20 +27,23 @@
 	defaultHgrcPath  string = "/opt/gitlab/etc/docker.hgrc:/etc/gitlab/heptapod.hgrc"
 )
 
+type yamlDuration time.Duration
+
 type ServerConfig struct {
-	Listen                     string   `yaml:"listen,omitempty"`
-	ProxyProtocol              bool     `yaml:"proxy_protocol,omitempty"`
-	ProxyPolicy                string   `yaml:"proxy_policy,omitempty"`
-	WebListen                  string   `yaml:"web_listen,omitempty"`
-	ConcurrentSessionsLimit    int64    `yaml:"concurrent_sessions_limit,omitempty"`
-	ClientAliveIntervalSeconds int64    `yaml:"client_alive_interval,omitempty"`
-	GracePeriodSeconds         uint64   `yaml:"grace_period"`
-	ReadinessProbe             string   `yaml:"readiness_probe"`
-	LivenessProbe              string   `yaml:"liveness_probe"`
-	HostKeyFiles               []string `yaml:"host_key_files,omitempty"`
-	MACs                       []string `yaml:"macs"`
-	KexAlgorithms              []string `yaml:"kex_algorithms"`
-	Ciphers                    []string `yaml:"ciphers"`
+	Listen                  string       `yaml:"listen,omitempty"`
+	ProxyProtocol           bool         `yaml:"proxy_protocol,omitempty"`
+	ProxyPolicy             string       `yaml:"proxy_policy,omitempty"`
+	WebListen               string       `yaml:"web_listen,omitempty"`
+	ConcurrentSessionsLimit int64        `yaml:"concurrent_sessions_limit,omitempty"`
+	ClientAliveInterval     yamlDuration `yaml:"client_alive_interval,omitempty"`
+	GracePeriod             yamlDuration `yaml:"grace_period"`
+	ProxyHeaderTimeout      yamlDuration `yaml:"proxy_header_timeout"`
+	ReadinessProbe          string       `yaml:"readiness_probe"`
+	LivenessProbe           string       `yaml:"liveness_probe"`
+	HostKeyFiles            []string     `yaml:"host_key_files,omitempty"`
+	MACs                    []string     `yaml:"macs"`
+	KexAlgorithms           []string     `yaml:"kex_algorithms"`
+	Ciphers                 []string     `yaml:"ciphers"`
 }
 
 type HttpSettingsConfig struct {
@@ -106,13 +109,14 @@
 	}
 
 	DefaultServerConfig = ServerConfig{
-		Listen:                     "[::]:22",
-		WebListen:                  "localhost:9122",
-		ConcurrentSessionsLimit:    10,
-		GracePeriodSeconds:         10,
-		ClientAliveIntervalSeconds: 15,
-		ReadinessProbe:             "/start",
-		LivenessProbe:              "/health",
+		Listen:                  "[::]:22",
+		WebListen:               "localhost:9122",
+		ConcurrentSessionsLimit: 10,
+		GracePeriod:             yamlDuration(10 * time.Second),
+		ClientAliveInterval:     yamlDuration(15 * time.Second),
+		ProxyHeaderTimeout:      yamlDuration(500 * time.Millisecond),
+		ReadinessProbe:          "/start",
+		LivenessProbe:           "/health",
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
@@ -121,12 +125,15 @@
 	}
 )
 
-func (sc *ServerConfig) ClientAliveInterval() time.Duration {
-	return time.Duration(sc.ClientAliveIntervalSeconds) * time.Second
-}
+func (d *yamlDuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	var intDuration int
+	if err := unmarshal(&intDuration); err != nil {
+		return unmarshal((*time.Duration)(d))
+	}
 
-func (sc *ServerConfig) GracePeriod() time.Duration {
-	return time.Duration(sc.GracePeriodSeconds) * time.Second
+	*d = yamlDuration(time.Duration(intDuration) * time.Second)
+
+	return nil
 }
 
 func (c *Config) ApplyGlobalState() {
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -3,9 +3,11 @@
 import (
 	"os"
 	"testing"
+	"time"
 
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/stretchr/testify/require"
+	yaml "gopkg.in/yaml.v2"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
@@ -58,3 +60,40 @@
 
 	require.Equal(t, expectedMetricNames, actualNames)
 }
+
+func TestNewFromDir(t *testing.T) {
+	testhelper.PrepareTestRootDir(t)
+
+	cfg, err := NewFromDir(testhelper.TestRoot)
+	require.NoError(t, err)
+
+	require.Equal(t, 10*time.Second, time.Duration(cfg.Server.GracePeriod))
+	require.Equal(t, 1*time.Minute, time.Duration(cfg.Server.ClientAliveInterval))
+	require.Equal(t, 500*time.Millisecond, time.Duration(cfg.Server.ProxyHeaderTimeout))
+}
+
+func TestYAMLDuration(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		data     string
+		duration time.Duration
+	}{
+		{"seconds assumed by default", "duration: 10", 10 * time.Second},
+		{"milliseconds are parsed", "duration: 500ms", 500 * time.Millisecond},
+		{"minutes are parsed", "duration: 1m", 1 * time.Minute},
+	}
+
+	type durationCfg struct {
+		Duration yamlDuration `yaml:"duration"`
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			var cfg durationCfg
+			err := yaml.Unmarshal([]byte(tc.data), &cfg)
+			require.NoError(t, err)
+
+			require.Equal(t, tc.duration, time.Duration(cfg.Duration))
+		})
+	}
+}
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -46,8 +46,8 @@
 func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
-	if c.cfg.Server.ClientAliveIntervalSeconds > 0 {
-		ticker := time.NewTicker(c.cfg.Server.ClientAliveInterval())
+	if c.cfg.Server.ClientAliveInterval > 0 {
+		ticker := time.NewTicker(time.Duration(c.cfg.Server.ClientAliveInterval))
 		defer ticker.Stop()
 		go c.sendKeepAliveMsg(ctx, ticker)
 	}
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -80,7 +80,7 @@
 }
 
 func setup(sessionsNum int64, newChannel *fakeNewChannel) (*connection, chan ssh.NewChannel) {
-	cfg := &config.Config{Server: config.ServerConfig{ConcurrentSessionsLimit: sessionsNum, ClientAliveIntervalSeconds: 1}}
+	cfg := &config.Config{Server: config.ServerConfig{ConcurrentSessionsLimit: sessionsNum}}
 	conn := newConnection(cfg, "127.0.0.1:50000", &ssh.ServerConn{&fakeConn{}, nil})
 
 	chans := make(chan ssh.NewChannel, 1)
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -26,7 +26,6 @@
 	StatusReady
 	StatusOnShutdown
 	StatusClosed
-	ProxyHeaderTimeout = 90 * time.Second
 )
 
 type Server struct {
@@ -97,7 +96,7 @@
 		sshListener = &proxyproto.Listener{
 			Listener:          sshListener,
 			Policy:            s.requirePolicy,
-			ReadHeaderTimeout: ProxyHeaderTimeout,
+			ReadHeaderTimeout: time.Duration(s.Config.Server.ProxyHeaderTimeout),
 		}
 
 		log.ContextLogger(ctx).Info("Proxy protocol is enabled")
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -265,7 +265,6 @@
 	cfg.User = user
 	cfg.Server.Listen = serverUrl
 	cfg.Server.ConcurrentSessionsLimit = 1
-	cfg.Server.ClientAliveIntervalSeconds = 15
 	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
 
 	s, err := NewServer(cfg)
diff --git a/internal/testhelper/testdata/testroot/config.yml b/internal/testhelper/testdata/testroot/config.yml
--- a/internal/testhelper/testdata/testroot/config.yml
+++ b/internal/testhelper/testdata/testroot/config.yml
@@ -0,0 +1,4 @@
+sshd:
+  grace_period: 10
+  client_alive_interval: 1m
+  proxy_header_timeout: 500ms
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1664447010 -7200
#      Thu Sep 29 12:23:30 2022 +0200
# Branch heptapod
# Node ID 5057028abd0982de557cdf6395653cb88a5e96e5
# Parent  aaebb02398004cb60c56d98824084bf9a2c17073
Added tag heptapod-14.5.0 for changeset aaebb0239800

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -46,3 +46,4 @@
 fca0cc645e5bbe898f18befe0f31f51f091db81b heptapod-14.3.1
 61f29699614da28ec795cec912a75f398df84af3 heptapod-14.3.2
 8696661a67a6e7816fca8066dcbdc66b261feb5b heptapod-14.4.0
+aaebb02398004cb60c56d98824084bf9a2c17073 heptapod-14.5.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1664536178 -7200
#      Fri Sep 30 13:09:38 2022 +0200
# Branch heptapod
# Node ID 69c79e2a7c7cd53de504e610f85e0cc61bf184bd
# Parent  5057028abd0982de557cdf6395653cb88a5e96e5
# Parent  363bcf081e7da20b57c13161133c718c3aa63c4c
Merged upstream v14.6.0 into Heptapod Shell

and updated Heptapod version files

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,12 @@
+v14.6.0
+
+- Exclude Gitaly unavailable error from error rate !641
+- Downgrade auth EOF messages from warning to debug !641
+- Display constistently in gitlab-sshd and gitlab-shell !641
+- Downgrade host key mismatch messages from warning to debug !639
+- Introduce a GitLab-SSHD server version during handshake !640
+- Narrow supported kex algorithms !638
+
 v14.5.0
 
 - Make ProxyHeaderTimeout configurable !635
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,14 +10,18 @@
 
 The corresponding tag is heptapod-x.y.z
 
-## 14.4.0
+## 14.6.0
 
-- Bump to upstream GitLab Shell v14.4.0
+- Bump to upstream GitLab Shell v14.6.0
 
 ## 14.5.0
 
 - Bump to upstream GitLab Shell v14.5.0
 
+## 14.4.0
+
+- Bump to upstream GitLab Shell v14.4.0
+
 ## 14.3.2
 
 - Bump to upstream GitLab Shell v14.3.1
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-14.5.0
+14.6.0
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.5.0
+14.6.0
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -105,7 +105,7 @@
   # Specifies the available message authentication code algorithms that are used for protecting data integrity
   macs: [hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1]
   # Specifies the available Key Exchange algorithms
-  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1]
+  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256]
   # Specified the ciphers allowed
   ciphers: [aes128-gcm@openssh.com, chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-ctr, aes192-ctr,aes256-ctr]
   # SSH host key files.
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -11,6 +11,7 @@
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.2
 	github.com/prometheus/client_golang v1.12.1
+	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
 	gitlab.com/gitlab-org/labkit v1.14.0
@@ -59,7 +60,6 @@
 	github.com/prometheus/procfs v0.7.3 // indirect
 	github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect
 	github.com/shirou/gopsutil/v3 v3.21.2 // indirect
-	github.com/sirupsen/logrus v1.8.1 // indirect
 	github.com/tinylib/msgp v1.1.2 // indirect
 	github.com/tklauser/go-sysconf v0.3.4 // indirect
 	github.com/tklauser/numcpus v0.2.1 // indirect
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -41,7 +41,7 @@
 	require.NoError(t, err)
 
 	var actualNames []string
-	for _, m := range ms[0:10] {
+	for _, m := range ms[0:9] {
 		actualNames = append(actualNames, m.GetName())
 	}
 
@@ -49,7 +49,6 @@
 		"gitlab_shell_http_in_flight_requests",
 		"gitlab_shell_http_request_duration_seconds",
 		"gitlab_shell_http_requests_total",
-		"gitlab_shell_sshd_canceled_sessions",
 		"gitlab_shell_sshd_concurrent_limited_sessions_total",
 		"gitlab_shell_sshd_in_flight_connections",
 		"gitlab_shell_sshd_session_duration_seconds",
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -58,13 +58,11 @@
 	exitStatus, err := handler(childCtx, conn)
 
 	if err != nil {
-		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
-			ctxlog.WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Gitaly is unavailable")
+		ctxlog.WithError(err).WithFields(log.Fields{"exit_status": exitStatus}).Error("Failed to execute Git command")
 
-			return fmt.Errorf("The git server, Gitaly, is not available at this time. Please contact your administrator.")
+		if grpcstatus.Code(err) == grpccodes.Unavailable {
+			return grpcstatus.Error(grpccodes.Unavailable, "The git server, Gitaly, is not available at this time. Please contact your administrator.")
 		}
-
-		ctxlog.WithError(err).WithFields(log.Fields{"exit_status": exitStatus}).Error("Failed to execute Git command")
 	}
 
 	return err
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -84,10 +84,8 @@
 		},
 	)
 
-	expectedErr := grpcstatus.Error(grpccodes.Unavailable, "error")
-	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, expectedErr))
-
-	require.EqualError(t, err, "The git server, Gitaly, is not available at this time. Please contact your administrator.")
+	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, grpcstatus.Error(grpccodes.Unavailable, "error")))
+	require.Equal(t, err, grpcstatus.Error(grpccodes.Unavailable, "The git server, Gitaly, is not available at this time. Please contact your administrator."))
 }
 
 func TestRunGitalyCommandMetadata(t *testing.T) {
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -77,15 +77,6 @@
 		},
 	)
 
-	SshdCanceledSessions = promauto.NewCounter(
-		prometheus.CounterOpts{
-			Namespace: namespace,
-			Subsystem: sshdSubsystem,
-			Name:      sshdCanceledSessionsName,
-			Help:      "The number of canceled gitlab-sshd sessions.",
-		},
-	)
-
 	SliSshdSessionsTotal = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Name: sliSshdSessionsTotalName,
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -89,14 +89,7 @@
 			metrics.SliSshdSessionsTotal.Inc()
 			err := handler(ctx, channel, requests)
 			if err != nil {
-				if grpcstatus.Convert(err).Code() == grpccodes.Canceled {
-					metrics.SshdCanceledSessions.Inc()
-				} else {
-					var apiError *client.ApiError
-					if !errors.As(err, &apiError) {
-						metrics.SliSshdSessionsErrorsTotal.Inc()
-					}
-				}
+				c.trackError(err)
 			}
 
 			ctxlog.Info("connection: handle: done")
@@ -126,3 +119,17 @@
 		}
 	}
 }
+
+func (c *connection) trackError(err error) {
+	var apiError *client.ApiError
+	if errors.As(err, &apiError) {
+		return
+	}
+
+	grpcCode := grpcstatus.Code(err)
+	if grpcCode == grpccodes.Canceled || grpcCode == grpccodes.Unavailable {
+		return
+	}
+
+	metrics.SliSshdSessionsErrorsTotal.Inc()
+}
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -200,7 +200,6 @@
 	// https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#pkg-index
 	initialSessionsTotal := testutil.ToFloat64(metrics.SliSshdSessionsTotal)
 	initialSessionsErrorTotal := testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal)
-	initialCanceledSessions := testutil.ToFloat64(metrics.SshdCanceledSessions)
 
 	newChannel := &fakeNewChannel{channelType: "session"}
 
@@ -212,17 +211,15 @@
 
 	require.InDelta(t, initialSessionsTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
-	require.InDelta(t, initialCanceledSessions, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
 
 	conn, chans = setup(1, newChannel)
 	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
-		return grpcstatus.Error(grpccodes.Canceled, "error")
+		return grpcstatus.Error(grpccodes.Canceled, "canceled")
 	})
 
 	require.InDelta(t, initialSessionsTotal+2, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
-	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
 
 	conn, chans = setup(1, newChannel)
 	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
@@ -232,5 +229,13 @@
 
 	require.InDelta(t, initialSessionsTotal+3, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
-	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
+
+	conn, chans = setup(1, newChannel)
+	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		close(chans)
+		return grpcstatus.Error(grpccodes.Unavailable, "unavailable")
+	})
+
+	require.InDelta(t, initialSessionsTotal+4, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 }
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -16,13 +16,24 @@
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
-var supportedMACs = []string{
-	"hmac-sha2-256-etm@openssh.com",
-	"hmac-sha2-512-etm@openssh.com",
-	"hmac-sha2-256",
-	"hmac-sha2-512",
-	"hmac-sha1",
-}
+var (
+	supportedMACs = []string{
+		"hmac-sha2-256-etm@openssh.com",
+		"hmac-sha2-512-etm@openssh.com",
+		"hmac-sha2-256",
+		"hmac-sha2-512",
+		"hmac-sha1",
+	}
+
+	supportedKeyExchanges = []string{
+		"curve25519-sha256",
+		"curve25519-sha256@libssh.org",
+		"ecdh-sha2-nistp256",
+		"ecdh-sha2-nistp384",
+		"ecdh-sha2-nistp521",
+		"diffie-hellman-group14-sha256",
+	}
+)
 
 type serverConfig struct {
 	cfg                  *config.Config
@@ -92,6 +103,7 @@
 				},
 			}, nil
 		},
+		ServerVersion: "SSH-2.0-GitLab-SSHD",
 	}
 
 	if len(s.cfg.Server.MACs) > 0 {
@@ -102,6 +114,8 @@
 
 	if len(s.cfg.Server.KexAlgorithms) > 0 {
 		sshCfg.KeyExchanges = s.cfg.Server.KexAlgorithms
+	} else {
+		sshCfg.KeyExchanges = supportedKeyExchanges
 	}
 
 	if len(s.cfg.Server.Ciphers) > 0 {
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
--- a/internal/sshd/server_config_test.go
+++ b/internal/sshd/server_config_test.go
@@ -85,23 +85,13 @@
 	sshServerConfig := srvCfg.get(context.Background())
 
 	require.Equal(t, supportedMACs, sshServerConfig.MACs)
-	require.Nil(t, sshServerConfig.KeyExchanges)
+	require.Equal(t, supportedKeyExchanges, sshServerConfig.KeyExchanges)
 	require.Nil(t, sshServerConfig.Ciphers)
 
 	sshServerConfig.SetDefaults()
 
 	require.Equal(t, supportedMACs, sshServerConfig.MACs)
-
-	defaultKeyExchanges := []string{
-		"curve25519-sha256",
-		"curve25519-sha256@libssh.org",
-		"ecdh-sha2-nistp256",
-		"ecdh-sha2-nistp384",
-		"ecdh-sha2-nistp521",
-		"diffie-hellman-group14-sha256",
-		"diffie-hellman-group14-sha1",
-	}
-	require.Equal(t, defaultKeyExchanges, sshServerConfig.KeyExchanges)
+	require.Equal(t, supportedKeyExchanges, sshServerConfig.KeyExchanges)
 
 	defaultCiphers := []string{
 		"aes128-gcm@openssh.com",
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -8,11 +8,14 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 	"golang.org/x/crypto/ssh"
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
 
 	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
@@ -158,10 +161,12 @@
 
 	cmd, err := shellCmd.NewWithKey(s.gitlabKeyId, env, s.cfg, rw)
 	if err != nil {
-		if !errors.Is(err, disallowedcommand.Error) {
-			s.toStderr(ctx, "Failed to parse command: %v\n", err.Error())
+		if errors.Is(err, disallowedcommand.Error) {
+			s.toStderr(ctx, "ERROR: Unknown command: %v\n", s.execCmd)
+		} else {
+			s.toStderr(ctx, "ERROR: Failed to parse command: %v\n", err.Error())
 		}
-		s.toStderr(ctx, "Unknown command: %v\n", s.execCmd)
+
 		return 128, err
 	}
 
@@ -169,7 +174,11 @@
 	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("session: handleShell: executing command")
 
 	if err := cmd.Execute(ctx); err != nil {
-		s.toStderr(ctx, "remote: ERROR: %v\n", err.Error())
+		grpcStatus := grpcstatus.Convert(err)
+		if grpcStatus.Code() != grpccodes.Internal {
+			s.toStderr(ctx, "ERROR: %v\n", grpcStatus.Message())
+		}
+
 		return 1, err
 	}
 
@@ -181,7 +190,7 @@
 func (s *session) toStderr(ctx context.Context, format string, args ...interface{}) {
 	out := fmt.Sprintf(format, args...)
 	log.WithContextFields(ctx, log.Fields{"stderr": out}).Debug("session: toStderr: output")
-	fmt.Fprint(s.channel.Stderr(), out)
+	console.DisplayWarningMessage(out, s.channel.Stderr())
 }
 
 func (s *session) exit(ctx context.Context, status uint32) {
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -13,6 +13,7 @@
 
 	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
 )
 
 type fakeChannel struct {
@@ -160,14 +161,14 @@
 		{
 			desc:              "fails to parse command",
 			cmd:               `\`,
-			errMsg:            "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
+			errMsg:            "ERROR: Failed to parse command: Invalid SSH command: invalid command line string\n",
 			gitlabKeyId:       "root",
 			expectedErrString: "Invalid SSH command: invalid command line string",
 			expectedExitCode:  128,
 		}, {
 			desc:              "specified command is unknown",
 			cmd:               "unknown-command",
-			errMsg:            "Unknown command: unknown-command\n",
+			errMsg:            "ERROR: Unknown command: unknown-command\n",
 			gitlabKeyId:       "root",
 			expectedErrString: "Disallowed command",
 			expectedExitCode:  128,
@@ -175,7 +176,7 @@
 			desc:              "fails to parse command",
 			cmd:               "discover",
 			gitlabKeyId:       "",
-			errMsg:            "remote: ERROR: Failed to get username: who='' is invalid\n",
+			errMsg:            "ERROR: Failed to get username: who='' is invalid\n",
 			expectedErrString: "Failed to get username: who='' is invalid",
 			expectedExitCode:  1,
 		}, {
@@ -208,7 +209,14 @@
 			}
 
 			require.Equal(t, tc.expectedExitCode, exitCode)
-			require.Equal(t, tc.errMsg, out.String())
+
+			formattedErr := &bytes.Buffer{}
+			if tc.errMsg != "" {
+				console.DisplayWarningMessage(tc.errMsg, formattedErr)
+				require.Equal(t, formattedErr.String(), out.String())
+			} else {
+				require.Equal(t, tc.errMsg, out.String())
+			}
 		})
 	}
 }
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -10,6 +10,7 @@
 	"time"
 
 	"github.com/pires/go-proxyproto"
+	"github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -38,6 +39,18 @@
 	serverConfig *serverConfig
 }
 
+func logSSHInitError(ctxlog *logrus.Entry, err error) {
+	msg := "server: handleConn: failed to initialize SSH connection"
+
+	logger := ctxlog.WithError(err)
+
+	if strings.Contains(err.Error(), "no common algorithm for host key") || err.Error() == "EOF" {
+		logger.Debug(msg)
+	} else {
+		logger.Warn(msg)
+	}
+}
+
 func NewServer(cfg *config.Config) (*Server, error) {
 	serverConfig, err := newServerConfig(cfg)
 	if err != nil {
@@ -168,11 +181,11 @@
 		}
 	}()
 
-	ctxlog.Info("server: handleConn: start")
+	ctxlog.Debug("server: handleConn: start")
 
 	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
 	if err != nil {
-		ctxlog.WithError(err).Warn("server: handleConn: failed to initialize SSH connection")
+		logSSHInitError(ctxlog, err)
 		return
 	}
 	go ssh.DiscardRequests(reqs)
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1664543160 -7200
#      Fri Sep 30 15:06:00 2022 +0200
# Branch heptapod
# Node ID 43b06bc72118823afd19227f042e8117eeb1b6a9
# Parent  69c79e2a7c7cd53de504e610f85e0cc61bf184bd
Added tag heptapod-14.6.0 for changeset 69c79e2a7c7c

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -47,3 +47,4 @@
 61f29699614da28ec795cec912a75f398df84af3 heptapod-14.3.2
 8696661a67a6e7816fca8066dcbdc66b261feb5b heptapod-14.4.0
 aaebb02398004cb60c56d98824084bf9a2c17073 heptapod-14.5.0
+69c79e2a7c7cd53de504e610f85e0cc61bf184bd heptapod-14.6.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1664547977 -7200
#      Fri Sep 30 16:26:17 2022 +0200
# Branch heptapod
# Node ID 54e1067b740ecd4e2af0b52e4b2472366aeeb466
# Parent  43b06bc72118823afd19227f042e8117eeb1b6a9
# Parent  c54d0c6dfdb2076fb1a80dfd40886d8d98eb9a98
Merged upstream v14.7.0 into Heptapod Shell

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,12 @@
+v14.7.0
+
+- Abort long-running unauthenticated SSH connections !647
+- Close the connection when context is canceled !646
+
+v14.6.1
+
+- Return support for diffie-hellman-group14-sha1 !644
+
 v14.6.0
 
 - Exclude Gitaly unavailable error from error rate !641
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 14.7.0
+
+- Bump to upstream GitLab Shell v14.7.0
+
 ## 14.6.0
 
 - Bump to upstream GitLab Shell v14.6.0
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-14.6.0
+14.7.0
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.6.0
+14.7.0
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -92,10 +92,12 @@
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
-  # Sets an interval after which server will send keepalive message to a client
+  # Sets an interval after which server will send keepalive message to a client. Defaults to 15s.
   client_alive_interval: 15
-  # The server waits for this time (in seconds) for the ongoing connections to complete before shutting down. Defaults to 10.
+  # The server waits for this time for the ongoing connections to complete before shutting down. Defaults to 10s.
   grace_period: 10
+  # The server disconnects after this time if the user has not successfully logged in. Defaults to 60s.
+  login_grace_time: 60
   # A short timeout to decide to abort the connection if the protocol header is not seen within it. Defaults to 500ms
   proxy_header_timeout: 500ms
   # The endpoint that returns 200 OK if the server is ready to receive incoming connections; otherwise, it returns 503 Service Unavailable. Defaults to "/start".
@@ -105,7 +107,7 @@
   # Specifies the available message authentication code algorithms that are used for protecting data integrity
   macs: [hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1]
   # Specifies the available Key Exchange algorithms
-  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256]
+  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1]
   # Specified the ciphers allowed
   ciphers: [aes128-gcm@openssh.com, chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-ctr, aes192-ctr,aes256-ctr]
   # SSH host key files.
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -27,7 +27,7 @@
 	defaultHgrcPath  string = "/opt/gitlab/etc/docker.hgrc:/etc/gitlab/heptapod.hgrc"
 )
 
-type yamlDuration time.Duration
+type YamlDuration time.Duration
 
 type ServerConfig struct {
 	Listen                  string       `yaml:"listen,omitempty"`
@@ -35,9 +35,10 @@
 	ProxyPolicy             string       `yaml:"proxy_policy,omitempty"`
 	WebListen               string       `yaml:"web_listen,omitempty"`
 	ConcurrentSessionsLimit int64        `yaml:"concurrent_sessions_limit,omitempty"`
-	ClientAliveInterval     yamlDuration `yaml:"client_alive_interval,omitempty"`
-	GracePeriod             yamlDuration `yaml:"grace_period"`
-	ProxyHeaderTimeout      yamlDuration `yaml:"proxy_header_timeout"`
+	ClientAliveInterval     YamlDuration `yaml:"client_alive_interval,omitempty"`
+	GracePeriod             YamlDuration `yaml:"grace_period"`
+	ProxyHeaderTimeout      YamlDuration `yaml:"proxy_header_timeout"`
+	LoginGraceTime          YamlDuration `yaml:"login_grace_time"`
 	ReadinessProbe          string       `yaml:"readiness_probe"`
 	LivenessProbe           string       `yaml:"liveness_probe"`
 	HostKeyFiles            []string     `yaml:"host_key_files,omitempty"`
@@ -112,9 +113,10 @@
 		Listen:                  "[::]:22",
 		WebListen:               "localhost:9122",
 		ConcurrentSessionsLimit: 10,
-		GracePeriod:             yamlDuration(10 * time.Second),
-		ClientAliveInterval:     yamlDuration(15 * time.Second),
-		ProxyHeaderTimeout:      yamlDuration(500 * time.Millisecond),
+		GracePeriod:             YamlDuration(10 * time.Second),
+		ClientAliveInterval:     YamlDuration(15 * time.Second),
+		ProxyHeaderTimeout:      YamlDuration(500 * time.Millisecond),
+		LoginGraceTime:          YamlDuration(60 * time.Second),
 		ReadinessProbe:          "/start",
 		LivenessProbe:           "/health",
 		HostKeyFiles: []string{
@@ -125,13 +127,13 @@
 	}
 )
 
-func (d *yamlDuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
+func (d *YamlDuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	var intDuration int
 	if err := unmarshal(&intDuration); err != nil {
 		return unmarshal((*time.Duration)(d))
 	}
 
-	*d = yamlDuration(time.Duration(intDuration) * time.Second)
+	*d = YamlDuration(time.Duration(intDuration) * time.Second)
 
 	return nil
 }
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -83,7 +83,7 @@
 	}
 
 	type durationCfg struct {
-		Duration yamlDuration `yaml:"duration"`
+		Duration YamlDuration `yaml:"duration"`
 	}
 
 	for _, tc := range testCases {
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -3,6 +3,8 @@
 import (
 	"context"
 	"errors"
+	"net"
+	"strings"
 	"time"
 
 	"golang.org/x/crypto/ssh"
@@ -22,52 +24,95 @@
 var EOFTimeout = 10 * time.Second
 
 type connection struct {
-	cfg                *config.Config
-	concurrentSessions *semaphore.Weighted
-	remoteAddr         string
-	sconn              *ssh.ServerConn
-	maxSessions        int64
+	cfg                      *config.Config
+	concurrentSessions       *semaphore.Weighted
+	nconn                    net.Conn
+	maxSessions              int64
+	remoteAddr               string
+	started                  time.Time
+	establishSessionDuration float64
 }
 
-type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request) error
+type channelHandler func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error
 
-func newConnection(cfg *config.Config, remoteAddr string, sconn *ssh.ServerConn) *connection {
+func newConnection(cfg *config.Config, nconn net.Conn) *connection {
 	maxSessions := cfg.Server.ConcurrentSessionsLimit
 
 	return &connection{
 		cfg:                cfg,
 		maxSessions:        maxSessions,
 		concurrentSessions: semaphore.NewWeighted(maxSessions),
-		remoteAddr:         remoteAddr,
-		sconn:              sconn,
+		nconn:              nconn,
+		remoteAddr:         nconn.RemoteAddr().String(),
+		started:            time.Now(),
 	}
 }
 
-func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
-	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+func (c *connection) handle(ctx context.Context, srvCfg *ssh.ServerConfig, handler channelHandler) {
+	sconn, chans, err := c.initServerConn(ctx, srvCfg)
+	if err != nil {
+		return
+	}
 
 	if c.cfg.Server.ClientAliveInterval > 0 {
 		ticker := time.NewTicker(time.Duration(c.cfg.Server.ClientAliveInterval))
 		defer ticker.Stop()
-		go c.sendKeepAliveMsg(ctx, ticker)
+		go c.sendKeepAliveMsg(ctx, sconn, ticker)
+	}
+
+	c.handleRequests(ctx, sconn, chans, handler)
+
+	reason := sconn.Wait()
+	log.WithContextFields(ctx, log.Fields{
+		"duration_s":                   time.Since(c.started).Seconds(),
+		"establish_session_duration_s": c.establishSessionDuration,
+		"reason":                       reason,
+	}).Info("server: handleConn: done")
+}
+
+func (c *connection) initServerConn(ctx context.Context, srvCfg *ssh.ServerConfig) (*ssh.ServerConn, <-chan ssh.NewChannel, error) {
+	if c.cfg.Server.LoginGraceTime > 0 {
+		c.nconn.SetDeadline(time.Now().Add(time.Duration(c.cfg.Server.LoginGraceTime)))
+		defer c.nconn.SetDeadline(time.Time{})
 	}
 
+	sconn, chans, reqs, err := ssh.NewServerConn(c.nconn, srvCfg)
+	if err != nil {
+		msg := "connection: initServerConn: failed to initialize SSH connection"
+		logger := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr}).WithError(err)
+
+		if strings.Contains(err.Error(), "no common algorithm for host key") || err.Error() == "EOF" {
+			logger.Debug(msg)
+		} else {
+			logger.Warn(msg)
+		}
+
+		return nil, nil, err
+	}
+	go ssh.DiscardRequests(reqs)
+
+	return sconn, chans, err
+}
+
+func (c *connection) handleRequests(ctx context.Context, sconn *ssh.ServerConn, chans <-chan ssh.NewChannel, handler channelHandler) {
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+
 	for newChannel := range chans {
 		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
-			ctxlog.Info("connection: handle: unknown channel type")
+			ctxlog.Info("connection: handleRequests: unknown channel type")
 			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
 			continue
 		}
 		if !c.concurrentSessions.TryAcquire(1) {
-			ctxlog.Info("connection: handle: too many concurrent sessions")
+			ctxlog.Info("connection: handleRequests: too many concurrent sessions")
 			newChannel.Reject(ssh.ResourceShortage, "too many concurrent sessions")
 			metrics.SshdHitMaxSessions.Inc()
 			continue
 		}
 		channel, requests, err := newChannel.Accept()
 		if err != nil {
-			ctxlog.WithError(err).Error("connection: handle: accepting channel failed")
+			ctxlog.WithError(err).Error("connection: handleRequests: accepting channel failed")
 			c.concurrentSessions.Release(1)
 			continue
 		}
@@ -76,6 +121,8 @@
 			defer func(started time.Time) {
 				metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
 			}(time.Now())
+			c.establishSessionDuration = time.Since(c.started).Seconds()
+			metrics.SshdSessionEstablishedDuration.Observe(c.establishSessionDuration)
 
 			defer c.concurrentSessions.Release(1)
 
@@ -87,12 +134,12 @@
 			}()
 
 			metrics.SliSshdSessionsTotal.Inc()
-			err := handler(ctx, channel, requests)
+			err := handler(sconn, channel, requests)
 			if err != nil {
 				c.trackError(err)
 			}
 
-			ctxlog.Info("connection: handle: done")
+			ctxlog.Info("connection: handleRequests: done")
 		}()
 	}
 
@@ -105,7 +152,7 @@
 	c.concurrentSessions.Acquire(ctx, c.maxSessions)
 }
 
-func (c *connection) sendKeepAliveMsg(ctx context.Context, ticker *time.Ticker) {
+func (c *connection) sendKeepAliveMsg(ctx context.Context, sconn *ssh.ServerConn, ticker *time.Ticker) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
 	for {
@@ -113,9 +160,9 @@
 		case <-ctx.Done():
 			return
 		case <-ticker.C:
-			ctxlog.Debug("session: handleShell: send keepalive message to a client")
+			ctxlog.Debug("connection: sendKeepAliveMsg: send keepalive message to a client")
 
-			c.sconn.SendRequest(KeepAliveMsg, true, nil)
+			sconn.SendRequest(KeepAliveMsg, true, nil)
 		}
 	}
 }
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -10,6 +10,7 @@
 	"github.com/prometheus/client_golang/prometheus/testutil"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
+	"golang.org/x/sync/semaphore"
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
@@ -81,7 +82,7 @@
 
 func setup(sessionsNum int64, newChannel *fakeNewChannel) (*connection, chan ssh.NewChannel) {
 	cfg := &config.Config{Server: config.ServerConfig{ConcurrentSessionsLimit: sessionsNum}}
-	conn := newConnection(cfg, "127.0.0.1:50000", &ssh.ServerConn{&fakeConn{}, nil})
+	conn := &connection{cfg: cfg, concurrentSessions: semaphore.NewWeighted(sessionsNum)}
 
 	chans := make(chan ssh.NewChannel, 1)
 	chans <- newChannel
@@ -95,7 +96,7 @@
 
 	numSessions := 0
 	require.NotPanics(t, func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 			numSessions += 1
 			close(chans)
 			panic("This is a panic")
@@ -113,7 +114,7 @@
 	conn, chans := setup(1, newChannel)
 
 	go func() {
-		conn.handle(context.Background(), chans, nil)
+		conn.handleRequests(context.Background(), nil, chans, nil)
 	}()
 
 	rejectionData := <-rejectCh
@@ -133,7 +134,7 @@
 	defer cancel()
 
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 			<-ctx.Done() // Keep the accepted channel open until the end of the test
 			return nil
 		})
@@ -148,7 +149,7 @@
 	conn, chans := setup(1, newChannel)
 
 	channelHandled := false
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		channelHandled = true
 		close(chans)
 		return nil
@@ -167,7 +168,7 @@
 
 	channelHandled := false
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 			channelHandled = true
 			return nil
 		})
@@ -185,12 +186,11 @@
 func TestClientAliveInterval(t *testing.T) {
 	f := &fakeConn{}
 
-	conn := newConnection(&config.Config{}, "127.0.0.1:50000", &ssh.ServerConn{f, nil})
-
 	ticker := time.NewTicker(time.Millisecond)
 	defer ticker.Stop()
 
-	go conn.sendKeepAliveMsg(context.Background(), ticker)
+	conn := &connection{}
+	go conn.sendKeepAliveMsg(context.Background(), &ssh.ServerConn{f, nil}, ticker)
 
 	require.Eventually(t, func() bool { return KeepAliveMsg == f.SentRequestName() }, time.Second, time.Millisecond)
 }
@@ -204,7 +204,7 @@
 	newChannel := &fakeNewChannel{channelType: "session"}
 
 	conn, chans := setup(1, newChannel)
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
 		return errors.New("custom error")
 	})
@@ -213,7 +213,7 @@
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 
 	conn, chans = setup(1, newChannel)
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
 		return grpcstatus.Error(grpccodes.Canceled, "canceled")
 	})
@@ -222,7 +222,7 @@
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 
 	conn, chans = setup(1, newChannel)
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
 		return &client.ApiError{"api error"}
 	})
@@ -231,7 +231,7 @@
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 
 	conn, chans = setup(1, newChannel)
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
 		return grpcstatus.Error(grpccodes.Unavailable, "unavailable")
 	})
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -32,6 +32,7 @@
 		"ecdh-sha2-nistp384",
 		"ecdh-sha2-nistp521",
 		"diffie-hellman-group14-sha256",
+		"diffie-hellman-group14-sha1",
 	}
 )
 
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -10,7 +10,6 @@
 	"time"
 
 	"github.com/pires/go-proxyproto"
-	"github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
@@ -39,18 +38,6 @@
 	serverConfig *serverConfig
 }
 
-func logSSHInitError(ctxlog *logrus.Entry, err error) {
-	msg := "server: handleConn: failed to initialize SSH connection"
-
-	logger := ctxlog.WithError(err)
-
-	if strings.Contains(err.Error(), "no common algorithm for host key") || err.Error() == "EOF" {
-		logger.Debug(msg)
-	} else {
-		logger.Warn(msg)
-	}
-}
-
 func NewServer(cfg *config.Config) (*Server, error) {
 	serverConfig, err := newServerConfig(cfg)
 	if err != nil {
@@ -159,18 +146,21 @@
 }
 
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
+	defer s.wg.Done()
+
 	metrics.SshdConnectionsInFlight.Inc()
 	defer metrics.SshdConnectionsInFlight.Dec()
 
-	remoteAddr := nconn.RemoteAddr().String()
-
-	defer s.wg.Done()
-	defer nconn.Close()
-
 	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
 	defer cancel()
+	go func() {
+		<-ctx.Done()
+		nconn.Close() // Close the connection when context is cancelled
+	}()
 
+	remoteAddr := nconn.RemoteAddr().String()
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": remoteAddr})
+	ctxlog.Debug("server: handleConn: start")
 
 	// Prevent a panic in a single connection from taking out the whole server
 	defer func() {
@@ -181,22 +171,8 @@
 		}
 	}()
 
-	ctxlog.Debug("server: handleConn: start")
-
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
-	if err != nil {
-		logSSHInitError(ctxlog, err)
-		return
-	}
-	go ssh.DiscardRequests(reqs)
-
-	started := time.Now()
-	var establishSessionDuration float64
-	conn := newConnection(s.Config, remoteAddr, sconn)
-	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) error {
-		establishSessionDuration = time.Since(started).Seconds()
-		metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
-
+	conn := newConnection(s.Config, nconn)
+	conn.handle(ctx, s.serverConfig.get(ctx), func(sconn *ssh.ServerConn, channel ssh.Channel, requests <-chan *ssh.Request) error {
 		session := &session{
 			cfg:         s.Config,
 			channel:     channel,
@@ -206,13 +182,6 @@
 
 		return session.handle(ctx, requests)
 	})
-
-	reason := sconn.Wait()
-	ctxlog.WithFields(log.Fields{
-		"duration_s":                   time.Since(started).Seconds(),
-		"establish_session_duration_s": establishSessionDuration,
-		"reason":                       reason,
-	}).Info("server: handleConn: done")
 }
 
 func (s *Server) requirePolicy(_ net.Addr) (proxyproto.Policy, error) {
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -222,6 +222,68 @@
 	require.Nil(t, s.Shutdown())
 }
 
+func TestClosingHangedConnections(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	s := setupServerWithContext(t, nil, ctx)
+
+	unauthenticatedRequestStatus := make(chan string)
+	completed := make(chan bool)
+
+	clientCfg := clientConfig(t)
+	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
+		unauthenticatedRequestStatus <- "authentication-started"
+		<-completed // Wait infinitely
+
+		return nil
+	}
+
+	go func() {
+		// Start an SSH connection that never ends
+		ssh.Dial("tcp", serverUrl, clientCfg)
+	}()
+
+	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
+
+	require.NoError(t, s.Shutdown())
+	cancel()
+	verifyStatus(t, s, StatusClosed)
+}
+
+func TestLoginGraceTime(t *testing.T) {
+	cfg := &config.Config{
+		Server: config.ServerConfig{
+			LoginGraceTime: config.YamlDuration(50 * time.Millisecond),
+		},
+	}
+	s := setupServerWithConfig(t, cfg)
+
+	unauthenticatedRequestStatus := make(chan string)
+	completed := make(chan bool)
+
+	clientCfg := clientConfig(t)
+	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
+		unauthenticatedRequestStatus <- "authentication-started"
+		<-completed // Wait infinitely
+
+		return nil
+	}
+
+	go func() {
+		// Start an SSH connection that never ends
+		ssh.Dial("tcp", serverUrl, clientCfg)
+	}()
+
+	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
+
+	// Shutdown the server and verify that it's closed
+	// If LoginGraceTime doesn't work, then the connection that runs infinitely, will stop it from closing.
+	// The close won't happen until the context is canceled like in TestClosingHangedConnections
+	require.NoError(t, s.Shutdown())
+	verifyStatus(t, s, StatusClosed)
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
@@ -231,6 +293,12 @@
 func setupServerWithConfig(t *testing.T, cfg *config.Config) *Server {
 	t.Helper()
 
+	return setupServerWithContext(t, cfg, context.Background())
+}
+
+func setupServerWithContext(t *testing.T, cfg *config.Config, ctx context.Context) *Server {
+	t.Helper()
+
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/authorized_keys",
@@ -270,7 +338,7 @@
 	s, err := NewServer(cfg)
 	require.NoError(t, err)
 
-	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
+	go func() { require.NoError(t, s.ListenAndServe(ctx)) }()
 	t.Cleanup(func() { s.Shutdown() })
 
 	verifyStatus(t, s, StatusReady)
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1664548247 -7200
#      Fri Sep 30 16:30:47 2022 +0200
# Branch heptapod
# Node ID fffd61a882079fd2402c56a951a8968e9e135844
# Parent  54e1067b740ecd4e2af0b52e4b2472366aeeb466
Added tag heptapod-14.7.0 for changeset 54e1067b740e

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -48,3 +48,4 @@
 8696661a67a6e7816fca8066dcbdc66b261feb5b heptapod-14.4.0
 aaebb02398004cb60c56d98824084bf9a2c17073 heptapod-14.5.0
 69c79e2a7c7cd53de504e610f85e0cc61bf184bd heptapod-14.6.0
+54e1067b740ecd4e2af0b52e4b2472366aeeb466 heptapod-14.7.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1664869865 -7200
#      Tue Oct 04 09:51:05 2022 +0200
# Branch heptapod
# Node ID cfb031a1e9de24b88498850cff796c224ba48edd
# Parent  fffd61a882079fd2402c56a951a8968e9e135844
# Parent  d27c5af605da53832cae29fa0f9a200c1447047b
Merged upstream v14.7.4 into Heptapod Shell

This is the version in use in GitLab 15.1 (up to 15.1.6), we're
updating Heptapod version files accordingly

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,6 +9,7 @@
 
 variables:
   DOCKER_VERSION: "20.10.15"
+  BUNDLE_FROZEN: "true"
 
 workflow:
   rules: &workflow_rules
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,20 @@
+v14.7.4
+
+- gitlab-sshd: Update crypto module to fix RSA keys with old gpg-agent !662
+
+v14.7.3
+
+- Ignore "not our ref" errors from gitlab-sshd error metrics !656
+
+v14.7.2
+
+- Exclude disallowed command from error rate !654
+
+v14.7.1
+
+- Log gitlab-sshd session level indicator errors !650
+- Improve establish session duration metrics !651
+
 v14.7.0
 
 - Abort long-running unauthenticated SSH connections !647
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -113,4 +113,4 @@
   rspec (~> 3.8.0)
 
 BUNDLED WITH
-   2.3.6
+   2.3.15
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 14.7.1
+
+- Bump to upstream GitLab Shell v14.7.4 (GitLab 15.1 series)
+
 ## 14.7.0
 
 - Bump to upstream GitLab Shell v14.7.0
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-14.7.0
+14.7.1
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -58,6 +58,44 @@
 
 There is also a rate-limiter in place in Gitaly, but the calls will never be made to Gitaly if the rate limit is exceeded in Gitlab Shell (Rails).
 
+## GitLab SaaS
+
+A diagram of the flow of `gitlab-shell` on GitLab.com:
+
+```mermaid
+graph LR
+    a2 --> b2
+    a2  --> b3
+    a2 --> b4
+    b2 --> c1
+    b3 --> c1
+    b4 --> c1
+    c2 --> d1
+    c2 --> d2
+    c2 --> d3
+    d1 --> e1
+    d2 --> e1
+    d3 --> e1
+    a1[Cloudflare] --> a2[TCP<br/> load balancer]
+    e1[Git]
+
+    subgraph HAProxy Fleet
+    b2[HAProxy]
+    b3[HAProxy]
+    b4[HAProxy]
+    end
+
+    subgraph GKE
+    c1[Internal TCP<br/> load balancer<br/>port 2222] --> c2[GitLab-shell<br/> pods]
+    end
+
+    subgraph Gitaly
+    d1[Gitaly]
+    d2[Gitaly]
+    d3[Gitaly]
+    end
+```
+
 ## Releasing
 
 See [PROCESS.md](./PROCESS.md)
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.7.0
+14.7.4
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -81,4 +81,4 @@
 	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
 )
 
-replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac
+replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -888,8 +888,8 @@
 gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
-gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac h1:qNUzqBTbEGGjF5Fp0NWz3rNmqamwchxM+QKUZYeOS1c=
-gitlab.com/gitlab-org/golang-crypto v0.0.0-20220518204012-9dd4a7273aac/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed h1:aXSyBpG6K/QsTGevZnpFoDR7Nwvn24RpkDoWe37B8eY=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -7,30 +7,33 @@
 	"strings"
 	"time"
 
+	"github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
-const KeepAliveMsg = "keepalive@openssh.com"
+const (
+	KeepAliveMsg   = "keepalive@openssh.com"
+	NotOurRefError = `exit status 128, stderr: "fatal: git upload-pack: not our ref `
+)
 
 var EOFTimeout = 10 * time.Second
 
 type connection struct {
-	cfg                      *config.Config
-	concurrentSessions       *semaphore.Weighted
-	nconn                    net.Conn
-	maxSessions              int64
-	remoteAddr               string
-	started                  time.Time
-	establishSessionDuration float64
+	cfg                *config.Config
+	concurrentSessions *semaphore.Weighted
+	nconn              net.Conn
+	maxSessions        int64
+	remoteAddr         string
 }
 
 type channelHandler func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error
@@ -44,7 +47,6 @@
 		concurrentSessions: semaphore.NewWeighted(maxSessions),
 		nconn:              nconn,
 		remoteAddr:         nconn.RemoteAddr().String(),
-		started:            time.Now(),
 	}
 }
 
@@ -63,11 +65,7 @@
 	c.handleRequests(ctx, sconn, chans, handler)
 
 	reason := sconn.Wait()
-	log.WithContextFields(ctx, log.Fields{
-		"duration_s":                   time.Since(c.started).Seconds(),
-		"establish_session_duration_s": c.establishSessionDuration,
-		"reason":                       reason,
-	}).Info("server: handleConn: done")
+	log.WithContextFields(ctx, log.Fields{"reason": reason}).Info("server: handleConn: done")
 }
 
 func (c *connection) initServerConn(ctx context.Context, srvCfg *ssh.ServerConfig) (*ssh.ServerConn, <-chan ssh.NewChannel, error) {
@@ -119,10 +117,10 @@
 
 		go func() {
 			defer func(started time.Time) {
-				metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
+				duration := time.Since(started).Seconds()
+				metrics.SshdSessionDuration.Observe(duration)
+				ctxlog.WithFields(log.Fields{"duration_s": duration}).Info("connection: handleRequests: done")
 			}(time.Now())
-			c.establishSessionDuration = time.Since(c.started).Seconds()
-			metrics.SshdSessionEstablishedDuration.Observe(c.establishSessionDuration)
 
 			defer c.concurrentSessions.Release(1)
 
@@ -136,10 +134,8 @@
 			metrics.SliSshdSessionsTotal.Inc()
 			err := handler(sconn, channel, requests)
 			if err != nil {
-				c.trackError(err)
+				c.trackError(ctxlog, err)
 			}
-
-			ctxlog.Info("connection: handleRequests: done")
 		}()
 	}
 
@@ -167,16 +163,23 @@
 	}
 }
 
-func (c *connection) trackError(err error) {
+func (c *connection) trackError(ctxlog *logrus.Entry, err error) {
 	var apiError *client.ApiError
 	if errors.As(err, &apiError) {
 		return
 	}
 
+	if errors.Is(err, disallowedcommand.Error) {
+		return
+	}
+
 	grpcCode := grpcstatus.Code(err)
 	if grpcCode == grpccodes.Canceled || grpcCode == grpccodes.Unavailable {
 		return
+	} else if grpcCode == grpccodes.Internal && strings.Contains(err.Error(), NotOurRefError) {
+		return
 	}
 
 	metrics.SliSshdSessionsErrorsTotal.Inc()
+	ctxlog.WithError(err).Warn("connection: session error")
 }
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -15,6 +15,7 @@
 	grpcstatus "google.golang.org/grpc/status"
 
 	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 )
@@ -212,30 +213,25 @@
 	require.InDelta(t, initialSessionsTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
 
-	conn, chans = setup(1, newChannel)
-	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
-		close(chans)
-		return grpcstatus.Error(grpccodes.Canceled, "canceled")
-	})
-
-	require.InDelta(t, initialSessionsTotal+2, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
-	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+	for i, ignoredError := range []struct {
+		desc string
+		err  error
+	}{
+		{"canceled requests", grpcstatus.Error(grpccodes.Canceled, "canceled")},
+		{"unavailable Gitaly", grpcstatus.Error(grpccodes.Unavailable, "unavailable")},
+		{"api error", &client.ApiError{"api error"}},
+		{"disallowed command", disallowedcommand.Error},
+		{"not our ref", grpcstatus.Error(grpccodes.Internal, `rpc error: code = Internal desc = cmd wait: exit status 128, stderr: "fatal: git upload-pack: not our ref 9106d18f6a1b8022f6517f479696f3e3ea5e68c1"`)},
+	} {
+		t.Run(ignoredError.desc, func(t *testing.T) {
+			conn, chans = setup(1, newChannel)
+			conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
+				close(chans)
+				return ignoredError.err
+			})
 
-	conn, chans = setup(1, newChannel)
-	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
-		close(chans)
-		return &client.ApiError{"api error"}
-	})
-
-	require.InDelta(t, initialSessionsTotal+3, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
-	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
-
-	conn, chans = setup(1, newChannel)
-	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
-		close(chans)
-		return grpcstatus.Error(grpccodes.Unavailable, "unavailable")
-	})
-
-	require.InDelta(t, initialSessionsTotal+4, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
-	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+			require.InDelta(t, initialSessionsTotal+2+float64(i), testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+			require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+		})
+	}
 }
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -5,6 +5,7 @@
 	"errors"
 	"fmt"
 	"reflect"
+	"time"
 
 	"gitlab.com/gitlab-org/labkit/log"
 	"golang.org/x/crypto/ssh"
@@ -16,6 +17,7 @@
 	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
 	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
 )
 
@@ -29,6 +31,7 @@
 	// State managed by the session
 	execCmd            string
 	gitProtocolVersion string
+	started            time.Time
 }
 
 type execRequest struct {
@@ -171,7 +174,12 @@
 	}
 
 	cmdName := reflect.TypeOf(cmd).String()
-	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("session: handleShell: executing command")
+
+	establishSessionDuration := time.Since(s.started).Seconds()
+	ctxlog.WithFields(log.Fields{
+		"env": env, "command": cmdName, "established_session_duration_s": establishSessionDuration,
+	}).Info("session: handleShell: executing command")
+	metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
 
 	if err := cmd.Execute(ctx); err != nil {
 		grpcStatus := grpcstatus.Convert(err)
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -178,6 +178,7 @@
 			channel:     channel,
 			gitlabKeyId: sconn.Permissions.Extensions["key-id"],
 			remoteAddr:  remoteAddr,
+			started:     time.Now(),
 		}
 
 		return session.handle(ctx, requests)
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1664870532 -7200
#      Tue Oct 04 10:02:12 2022 +0200
# Branch heptapod
# Node ID eb391a3351eed2440ab3be3d26f43c219b184699
# Parent  cfb031a1e9de24b88498850cff796c224ba48edd
Added tag heptapod-14.7.1 for changeset cfb031a1e9de

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -49,3 +49,4 @@
 aaebb02398004cb60c56d98824084bf9a2c17073 heptapod-14.5.0
 69c79e2a7c7cd53de504e610f85e0cc61bf184bd heptapod-14.6.0
 54e1067b740ecd4e2af0b52e4b2472366aeeb466 heptapod-14.7.0
+cfb031a1e9de24b88498850cff796c224ba48edd heptapod-14.7.1
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1668442235 -3600
#      Mon Nov 14 17:10:35 2022 +0100
# Branch heptapod
# Node ID cf818cb4e4fe5440a953da19e2e8650bddbf45d5
# Parent  eb391a3351eed2440ab3be3d26f43c219b184699
# Parent  00502e780098673e6e130e33b80b04710dca7ad5
Merged upstream GitLab Shell 14.9 into Heptapod Shell

As usual, we're setting the version to 14.9.0

The modules import path now includes a `v14` (virtual) segment,
apparently needed for Golang tooling to be able to use versions
instead of just commit hashes (see 91f2a74d0d6c), we're followint suit.

There was a complication found by the race detector CI job (amounting
to `go test -race`): `internal/sshd/connection_test.go` has a race
condition between the loop over data (tabular-oriented testing) and
the inner call. The puzzling part is that it is detected in Heptapod
context only (several runs on upstream v14.9.0 didn't find it)

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,14 @@
+v14.9.0
+
+- Update LabKit library to v1.16.0 !668
+
+v14.8.0
+
+- go: Bump major version to v14 !666
+- Pass original IP from PROXY requests to internal API calls !665
+- Fix make install copying the wrong binaries !664
+- gitlab-sshd: Add support for configuring host certificates !661
+
 v14.7.4
 
 - gitlab-sshd: Update crypto module to fix RSA keys with old gpg-agent !662
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 14.9.0
+
+- Bump to upstream GitLab Shell v14.9.0 (GitLab 15.2 series)
+
 ## 14.7.1
 
 - Bump to upstream GitLab Shell v14.7.4 (GitLab 15.1 series)
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-14.7.1
+14.9.0
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -66,7 +66,6 @@
 	mkdir -p $(DESTDIR)$(PREFIX)/bin/
 	install -m755 bin/check $(DESTDIR)$(PREFIX)/bin/check
 	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell
-	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-keys-check
-	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-principals-check
-	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-sshd
-
+	install -m755 bin/gitlab-shell-authorized-keys-check $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-keys-check
+	install -m755 bin/gitlab-shell-authorized-principals-check $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-principals-check
+	install -m755 bin/gitlab-sshd $(DESTDIR)$(PREFIX)/bin/gitlab-sshd
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.7.4
+14.9.0
diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -15,8 +15,8 @@
 	"github.com/golang-jwt/jwt/v4"
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 var (
@@ -76,6 +76,7 @@
 			testErrorMessage(t, client)
 			testAuthenticationHeader(t, client)
 			testJWTAuthenticationHeader(t, client)
+			testXForwardedForHeader(t, client)
 		})
 	}
 }
@@ -221,6 +222,21 @@
 	})
 }
 
+func testXForwardedForHeader(t *testing.T, client *GitlabNetClient) {
+	t.Run("X-Forwarded-For Header inserted if original address in context", func(t *testing.T) {
+		ctx := context.WithValue(context.Background(), OriginalRemoteIPContextKey{}, "196.7.0.238")
+		response, err := client.Get(ctx, "/x_forwarded_for")
+		require.NoError(t, err)
+		require.NotNil(t, response)
+
+		defer response.Body.Close()
+
+		responseBody, err := io.ReadAll(response.Body)
+		require.NoError(t, err)
+		require.Equal(t, "196.7.0.238", string(responseBody))
+	})
+}
+
 func buildRequests(t *testing.T, relativeURLRoot string) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
@@ -257,6 +273,12 @@
 			},
 		},
 		{
+			Path: "/api/v4/internal/x_forwarded_for",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				fmt.Fprint(w, r.Header.Get("X-Forwarded-For"))
+			},
+		},
+		{
 			Path: "/api/v4/internal/error",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -41,6 +41,9 @@
 	Msg string
 }
 
+// To use as the key in a Context to set an X-Forwarded-For header in a request
+type OriginalRemoteIPContextKey struct{}
+
 func (e *ApiError) Error() string {
 	return e.Msg
 }
@@ -150,6 +153,11 @@
 	}
 	request.Header.Set(apiSecretHeaderName, tokenString)
 
+	originalRemoteIP, ok := ctx.Value(OriginalRemoteIPContextKey{}).(string)
+	if ok {
+		request.Header.Add("X-Forwarded-For", originalRemoteIP)
+	}
+
 	request.Header.Add("Content-Type", "application/json")
 	request.Header.Add("User-Agent", c.userAgent)
 	request.Close = true
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -11,7 +11,7 @@
 	"time"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
 )
 
 func TestReadTimeout(t *testing.T) {
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -9,8 +9,8 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 //go:generate openssl req -newkey rsa:4096 -new -nodes -x509 -days 3650 -out ../internal/testhelper/testdata/testroot/certs/client/server.crt -keyout ../internal/testhelper/testdata/testroot/certs/client/key.pem -subj "/C=US/ST=California/L=San Francisco/O=GitLab/OU=GitLab-Shell/CN=localhost"
diff --git a/client/testserver/testserver.go b/client/testserver/testserver.go
--- a/client/testserver/testserver.go
+++ b/client/testserver/testserver.go
@@ -14,7 +14,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 var (
diff --git a/cmd/check/command/command.go b/cmd/check/command/command.go
--- a/cmd/check/command/command.go
+++ b/cmd/check/command/command.go
@@ -1,11 +1,11 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func New(config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/check/command/command_test.go b/cmd/check/command/command_test.go
--- a/cmd/check/command/command_test.go
+++ b/cmd/check/command/command_test.go
@@ -4,11 +4,11 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/cmd/check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/check/main.go b/cmd/check/main.go
--- a/cmd/check/main.go
+++ b/cmd/check/main.go
@@ -4,12 +4,12 @@
 	"fmt"
 	"os"
 
-	checkCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
+	checkCmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
 )
 
 func main() {
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command.go b/cmd/gitlab-shell-authorized-keys-check/command/command.go
--- a/cmd/gitlab-shell-authorized-keys-check/command/command.go
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command.go
@@ -1,12 +1,12 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command_test.go b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
--- a/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
@@ -4,12 +4,12 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-keys-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go
--- a/cmd/gitlab-shell-authorized-keys-check/main.go
+++ b/cmd/gitlab-shell-authorized-keys-check/main.go
@@ -4,13 +4,13 @@
 	"fmt"
 	"os"
 
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-keys-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
 )
 
 func main() {
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command.go b/cmd/gitlab-shell-authorized-principals-check/command/command.go
--- a/cmd/gitlab-shell-authorized-principals-check/command/command.go
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command.go
@@ -1,12 +1,12 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command_test.go b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
--- a/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
@@ -4,12 +4,12 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-principals-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go
--- a/cmd/gitlab-shell-authorized-principals-check/main.go
+++ b/cmd/gitlab-shell-authorized-principals-check/main.go
@@ -4,13 +4,13 @@
 	"fmt"
 	"os"
 
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-principals-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
 )
 
 func main() {
diff --git a/cmd/gitlab-shell/command/command.go b/cmd/gitlab-shell/command/command.go
--- a/cmd/gitlab-shell/command/command.go
+++ b/cmd/gitlab-shell/command/command.go
@@ -1,21 +1,21 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/hg"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/hg"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 func New(arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/gitlab-shell/command/command_test.go b/cmd/gitlab-shell/command/command_test.go
--- a/cmd/gitlab-shell/command/command_test.go
+++ b/cmd/gitlab-shell/command/command_test.go
@@ -5,20 +5,20 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -11,14 +11,14 @@
 	"gitlab.com/gitlab-org/labkit/fips"
 	"gitlab.com/gitlab-org/labkit/log"
 
-	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -24,7 +24,7 @@
 	"github.com/stretchr/testify/require"
 	gitalyClient "gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 	"golang.org/x/crypto/ssh"
 )
 
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -8,10 +8,10 @@
 	"syscall"
 	"time"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshd"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshd"
 
 	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/monitoring"
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -115,3 +115,7 @@
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
     - /run/secrets/ssh-hostkeys/ssh_host_ecdsa_key
     - /run/secrets/ssh-hostkeys/ssh_host_ed25519_key
+  host_key_certs:
+    - /run/secrets/ssh-hostkeys/ssh_host_rsa_key-cert.pub
+    - /run/secrets/ssh-hostkeys/ssh_host_ecdsa_key-cert.pub
+    - /run/secrets/ssh-hostkeys/ssh_host_ed25519_key-cert.pub
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module gitlab.com/gitlab-org/gitlab-shell
+module gitlab.com/gitlab-org/gitlab-shell/v14
 
 go 1.17
 
@@ -14,7 +14,7 @@
 	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
-	gitlab.com/gitlab-org/labkit v1.14.0
+	gitlab.com/gitlab-org/labkit v1.16.0
 	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/grpc v1.40.0
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -887,6 +887,7 @@
 gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059 h1:X7+3GQIxUpScXpIMCU5+sfpYvZyBIQ3GMlEosP7Jssw=
 gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
+gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2 h1:zz/4sD22gZqvAdBgd6gYLRIbvrgoQLDE7emPK0sXC6o=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed h1:aXSyBpG6K/QsTGevZnpFoDR7Nwvn24RpkDoWe37B8eY=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -895,8 +896,8 @@
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
 gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
 gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
-gitlab.com/gitlab-org/labkit v1.14.0 h1:LSrvHgybidPyH8fHnsy1GBghrLR4kFObFrtZwUfCgAI=
-gitlab.com/gitlab-org/labkit v1.14.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
+gitlab.com/gitlab-org/labkit v1.16.0 h1:Vm3NAMZ8RqAunXlvPWby3GJ2R35vsYGP6Uu0YjyMIlY=
+gitlab.com/gitlab-org/labkit v1.16.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
diff --git a/internal/command/authorizedkeys/authorized_keys.go b/internal/command/authorizedkeys/authorized_keys.go
--- a/internal/command/authorizedkeys/authorized_keys.go
+++ b/internal/command/authorizedkeys/authorized_keys.go
@@ -5,11 +5,11 @@
 	"fmt"
 	"strconv"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/keyline"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/keyline"
 )
 
 type Command struct {
diff --git a/internal/command/authorizedkeys/authorized_keys_test.go b/internal/command/authorizedkeys/authorized_keys_test.go
--- a/internal/command/authorizedkeys/authorized_keys_test.go
+++ b/internal/command/authorizedkeys/authorized_keys_test.go
@@ -9,10 +9,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/command/authorizedprincipals/authorized_principals.go b/internal/command/authorizedprincipals/authorized_principals.go
--- a/internal/command/authorizedprincipals/authorized_principals.go
+++ b/internal/command/authorizedprincipals/authorized_principals.go
@@ -4,10 +4,10 @@
 	"context"
 	"fmt"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/keyline"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/keyline"
 )
 
 type Command struct {
diff --git a/internal/command/authorizedprincipals/authorized_principals_test.go b/internal/command/authorizedprincipals/authorized_principals_test.go
--- a/internal/command/authorizedprincipals/authorized_principals_test.go
+++ b/internal/command/authorizedprincipals/authorized_principals_test.go
@@ -7,9 +7,9 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func TestExecute(t *testing.T) {
diff --git a/internal/command/command.go b/internal/command/command.go
--- a/internal/command/command.go
+++ b/internal/command/command.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/tracing"
 )
diff --git a/internal/command/command_test.go b/internal/command/command_test.go
--- a/internal/command/command_test.go
+++ b/internal/command/command_test.go
@@ -6,7 +6,7 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
diff --git a/internal/command/commandargs/shell.go b/internal/command/commandargs/shell.go
--- a/internal/command/commandargs/shell.go
+++ b/internal/command/commandargs/shell.go
@@ -6,7 +6,7 @@
 	"strings"
 
 	"github.com/mattn/go-shellwords"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 const (
diff --git a/internal/command/discover/discover.go b/internal/command/discover/discover.go
--- a/internal/command/discover/discover.go
+++ b/internal/command/discover/discover.go
@@ -4,10 +4,10 @@
 	"context"
 	"fmt"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Command struct {
diff --git a/internal/command/discover/discover_test.go b/internal/command/discover/discover_test.go
--- a/internal/command/discover/discover_test.go
+++ b/internal/command/discover/discover_test.go
@@ -10,10 +10,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/command/healthcheck/healthcheck.go b/internal/command/healthcheck/healthcheck.go
--- a/internal/command/healthcheck/healthcheck.go
+++ b/internal/command/healthcheck/healthcheck.go
@@ -4,9 +4,9 @@
 	"context"
 	"fmt"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/healthcheck"
 )
 
 var (
diff --git a/internal/command/healthcheck/healthcheck_test.go b/internal/command/healthcheck/healthcheck_test.go
--- a/internal/command/healthcheck/healthcheck_test.go
+++ b/internal/command/healthcheck/healthcheck_test.go
@@ -9,10 +9,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/healthcheck"
 )
 
 var (
diff --git a/internal/command/hg/hg.go b/internal/command/hg/hg.go
--- a/internal/command/hg/hg.go
+++ b/internal/command/hg/hg.go
@@ -10,11 +10,11 @@
 	"strings"
 	"syscall"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/hgaccesslevel"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/hgaccesslevel"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func Exec(filename string, args []string, env []string) error {
diff --git a/internal/command/hg/hg_test.go b/internal/command/hg/hg_test.go
--- a/internal/command/hg/hg_test.go
+++ b/internal/command/hg/hg_test.go
@@ -8,11 +8,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 type fakeExec struct {
diff --git a/internal/command/lfsauthenticate/lfsauthenticate.go b/internal/command/lfsauthenticate/lfsauthenticate.go
--- a/internal/command/lfsauthenticate/lfsauthenticate.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate.go
@@ -8,12 +8,12 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/lfsauthenticate"
 )
 
 const (
diff --git a/internal/command/lfsauthenticate/lfsauthenticate_test.go b/internal/command/lfsauthenticate/lfsauthenticate_test.go
--- a/internal/command/lfsauthenticate/lfsauthenticate_test.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate_test.go
@@ -10,13 +10,13 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestFailedRequests(t *testing.T) {
diff --git a/internal/command/personalaccesstoken/personalaccesstoken.go b/internal/command/personalaccesstoken/personalaccesstoken.go
--- a/internal/command/personalaccesstoken/personalaccesstoken.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken.go
@@ -10,10 +10,10 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/personalaccesstoken"
 )
 
 const (
diff --git a/internal/command/personalaccesstoken/personalaccesstoken_test.go b/internal/command/personalaccesstoken/personalaccesstoken_test.go
--- a/internal/command/personalaccesstoken/personalaccesstoken_test.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken_test.go
@@ -10,11 +10,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/personalaccesstoken"
 )
 
 var (
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -7,9 +7,9 @@
 
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/handler"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
diff --git a/internal/command/receivepack/gitalycall_test.go b/internal/command/receivepack/gitalycall_test.go
--- a/internal/command/receivepack/gitalycall_test.go
+++ b/internal/command/receivepack/gitalycall_test.go
@@ -8,12 +8,12 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestReceivePack(t *testing.T) {
diff --git a/internal/command/receivepack/receivepack.go b/internal/command/receivepack/receivepack.go
--- a/internal/command/receivepack/receivepack.go
+++ b/internal/command/receivepack/receivepack.go
@@ -3,12 +3,12 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/customaction"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/customaction"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 type Command struct {
diff --git a/internal/command/receivepack/receivepack_test.go b/internal/command/receivepack/receivepack_test.go
--- a/internal/command/receivepack/receivepack_test.go
+++ b/internal/command/receivepack/receivepack_test.go
@@ -7,11 +7,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestForbiddenAccess(t *testing.T) {
diff --git a/internal/command/shared/accessverifier/accessverifier.go b/internal/command/shared/accessverifier/accessverifier.go
--- a/internal/command/shared/accessverifier/accessverifier.go
+++ b/internal/command/shared/accessverifier/accessverifier.go
@@ -4,11 +4,11 @@
 	"context"
 	"errors"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 )
 
 type Response = accessverifier.Response
diff --git a/internal/command/shared/accessverifier/accessverifier_test.go b/internal/command/shared/accessverifier/accessverifier_test.go
--- a/internal/command/shared/accessverifier/accessverifier_test.go
+++ b/internal/command/shared/accessverifier/accessverifier_test.go
@@ -10,11 +10,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 )
 
 var (
diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -9,12 +9,12 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/pktline"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/pktline"
 )
 
 type Request struct {
diff --git a/internal/command/shared/customaction/customaction_test.go b/internal/command/shared/customaction/customaction_test.go
--- a/internal/command/shared/customaction/customaction_test.go
+++ b/internal/command/shared/customaction/customaction_test.go
@@ -10,10 +10,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 )
 
 func TestExecuteEOFSent(t *testing.T) {
diff --git a/internal/command/shared/hgaccesslevel/hgaccesslevel.go b/internal/command/shared/hgaccesslevel/hgaccesslevel.go
--- a/internal/command/shared/hgaccesslevel/hgaccesslevel.go
+++ b/internal/command/shared/hgaccesslevel/hgaccesslevel.go
@@ -5,10 +5,10 @@
 	"errors"
 	"fmt"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 )
 
 type Response = accessverifier.HgResponse
diff --git a/internal/command/twofactorrecover/twofactorrecover.go b/internal/command/twofactorrecover/twofactorrecover.go
--- a/internal/command/twofactorrecover/twofactorrecover.go
+++ b/internal/command/twofactorrecover/twofactorrecover.go
@@ -8,10 +8,10 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorrecover"
 )
 
 const readerLimit = 1024
diff --git a/internal/command/twofactorrecover/twofactorrecover_test.go b/internal/command/twofactorrecover/twofactorrecover_test.go
--- a/internal/command/twofactorrecover/twofactorrecover_test.go
+++ b/internal/command/twofactorrecover/twofactorrecover_test.go
@@ -11,11 +11,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorrecover"
 )
 
 var (
diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -7,10 +7,10 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
 )
 
 type Command struct {
diff --git a/internal/command/twofactorverify/twofactorverify_test.go b/internal/command/twofactorverify/twofactorverify_test.go
--- a/internal/command/twofactorverify/twofactorverify_test.go
+++ b/internal/command/twofactorverify/twofactorverify_test.go
@@ -10,11 +10,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
 )
 
 func setup(t *testing.T) []testserver.TestRequestHandler {
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -7,9 +7,9 @@
 
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/handler"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
diff --git a/internal/command/uploadarchive/gitalycall_test.go b/internal/command/uploadarchive/gitalycall_test.go
--- a/internal/command/uploadarchive/gitalycall_test.go
+++ b/internal/command/uploadarchive/gitalycall_test.go
@@ -8,12 +8,12 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestUploadArchive(t *testing.T) {
diff --git a/internal/command/uploadarchive/uploadarchive.go b/internal/command/uploadarchive/uploadarchive.go
--- a/internal/command/uploadarchive/uploadarchive.go
+++ b/internal/command/uploadarchive/uploadarchive.go
@@ -3,11 +3,11 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 type Command struct {
diff --git a/internal/command/uploadarchive/uploadarchive_test.go b/internal/command/uploadarchive/uploadarchive_test.go
--- a/internal/command/uploadarchive/uploadarchive_test.go
+++ b/internal/command/uploadarchive/uploadarchive_test.go
@@ -7,11 +7,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestForbiddenAccess(t *testing.T) {
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -7,9 +7,9 @@
 
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/handler"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -8,12 +8,12 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestUploadPack(t *testing.T) {
diff --git a/internal/command/uploadpack/uploadpack.go b/internal/command/uploadpack/uploadpack.go
--- a/internal/command/uploadpack/uploadpack.go
+++ b/internal/command/uploadpack/uploadpack.go
@@ -3,12 +3,12 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/customaction"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/customaction"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 type Command struct {
diff --git a/internal/command/uploadpack/uploadpack_test.go b/internal/command/uploadpack/uploadpack_test.go
--- a/internal/command/uploadpack/uploadpack_test.go
+++ b/internal/command/uploadpack/uploadpack_test.go
@@ -7,11 +7,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestForbiddenAccess(t *testing.T) {
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -12,9 +12,9 @@
 
 	yaml "gopkg.in/yaml.v2"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitaly"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 const (
@@ -42,6 +42,7 @@
 	ReadinessProbe          string       `yaml:"readiness_probe"`
 	LivenessProbe           string       `yaml:"liveness_probe"`
 	HostKeyFiles            []string     `yaml:"host_key_files,omitempty"`
+	HostCertFiles           []string     `yaml:"host_cert_files,omitempty"`
 	MACs                    []string     `yaml:"macs"`
 	KexAlgorithms           []string     `yaml:"kex_algorithms"`
 	Ciphers                 []string     `yaml:"ciphers"`
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -9,8 +9,8 @@
 	"github.com/stretchr/testify/require"
 	yaml "gopkg.in/yaml.v2"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 func TestConfigApplyGlobalState(t *testing.T) {
diff --git a/internal/executable/executable_test.go b/internal/executable/executable_test.go
--- a/internal/executable/executable_test.go
+++ b/internal/executable/executable_test.go
@@ -4,7 +4,7 @@
 	"errors"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 
 	"github.com/stretchr/testify/require"
 )
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
--- a/internal/gitaly/gitaly.go
+++ b/internal/gitaly/gitaly.go
@@ -17,7 +17,7 @@
 	"gitlab.com/gitlab-org/labkit/log"
 	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 type Command struct {
diff --git a/internal/gitaly/gitaly_test.go b/internal/gitaly/gitaly_test.go
--- a/internal/gitaly/gitaly_test.go
+++ b/internal/gitaly/gitaly_test.go
@@ -7,7 +7,7 @@
 	"github.com/prometheus/client_golang/prometheus/testutil"
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 func TestPrometheusMetrics(t *testing.T) {
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -3,14 +3,13 @@
 import (
 	"context"
 	"fmt"
-	"net"
 	"net/http"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 const (
@@ -86,7 +85,7 @@
 		request.KeyId = args.GitlabKeyId
 	}
 
-	request.CheckIp = parseIP(args.Env.RemoteAddr)
+	request.CheckIp = gitlabnet.ParseIP(args.Env.RemoteAddr)
 
 	response, err := c.client.Post(ctx, "/allowed", request)
 	if err != nil {
@@ -117,18 +116,3 @@
 func (r *Response) IsCustomAction() bool {
 	return r.StatusCode == http.StatusMultipleChoices
 }
-
-func parseIP(remoteAddr string) string {
-	// The remoteAddr field can be filled by:
-	// 1. An IP address via the SSH_CONNECTION environment variable
-	// 2. A host:port combination via the PROXY protocol
-	ip, _, err := net.SplitHostPort(remoteAddr)
-
-	// If we don't have a port or can't parse this address for some reason,
-	// just return the original string.
-	if err != nil {
-		return remoteAddr
-	}
-
-	return ip
-}
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -11,11 +11,11 @@
 
 	"github.com/stretchr/testify/require"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 var (
diff --git a/internal/gitlabnet/accessverifier/hg_client.go b/internal/gitlabnet/accessverifier/hg_client.go
--- a/internal/gitlabnet/accessverifier/hg_client.go
+++ b/internal/gitlabnet/accessverifier/hg_client.go
@@ -5,10 +5,10 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 type HgClient struct {
diff --git a/internal/gitlabnet/accessverifier/hg_client_test.go b/internal/gitlabnet/accessverifier/hg_client_test.go
--- a/internal/gitlabnet/accessverifier/hg_client_test.go
+++ b/internal/gitlabnet/accessverifier/hg_client_test.go
@@ -10,11 +10,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 func buildHgExpectedResponse() *HgResponse {
diff --git a/internal/gitlabnet/authorizedkeys/client.go b/internal/gitlabnet/authorizedkeys/client.go
--- a/internal/gitlabnet/authorizedkeys/client.go
+++ b/internal/gitlabnet/authorizedkeys/client.go
@@ -5,9 +5,9 @@
 	"fmt"
 	"net/url"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 const (
diff --git a/internal/gitlabnet/authorizedkeys/client_test.go b/internal/gitlabnet/authorizedkeys/client_test.go
--- a/internal/gitlabnet/authorizedkeys/client_test.go
+++ b/internal/gitlabnet/authorizedkeys/client_test.go
@@ -7,9 +7,9 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/gitlabnet/client.go b/internal/gitlabnet/client.go
--- a/internal/gitlabnet/client.go
+++ b/internal/gitlabnet/client.go
@@ -3,11 +3,12 @@
 import (
 	"encoding/json"
 	"fmt"
+	"net"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
@@ -34,3 +35,18 @@
 
 	return nil
 }
+
+func ParseIP(remoteAddr string) string {
+	// The remoteAddr field can be filled by:
+	// 1. An IP address via the SSH_CONNECTION environment variable
+	// 2. A host:port combination via the PROXY protocol
+	ip, _, err := net.SplitHostPort(remoteAddr)
+
+	// If we don't have a port or can't parse this address for some reason,
+	// just return the original string.
+	if err != nil {
+		return remoteAddr
+	}
+
+	return ip
+}
diff --git a/internal/gitlabnet/discover/client.go b/internal/gitlabnet/discover/client.go
--- a/internal/gitlabnet/discover/client.go
+++ b/internal/gitlabnet/discover/client.go
@@ -6,10 +6,10 @@
 	"net/http"
 	"net/url"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/discover/client_test.go b/internal/gitlabnet/discover/client_test.go
--- a/internal/gitlabnet/discover/client_test.go
+++ b/internal/gitlabnet/discover/client_test.go
@@ -8,11 +8,11 @@
 	"net/url"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/gitlabnet/healthcheck/client.go b/internal/gitlabnet/healthcheck/client.go
--- a/internal/gitlabnet/healthcheck/client.go
+++ b/internal/gitlabnet/healthcheck/client.go
@@ -5,9 +5,9 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 const (
diff --git a/internal/gitlabnet/healthcheck/client_test.go b/internal/gitlabnet/healthcheck/client_test.go
--- a/internal/gitlabnet/healthcheck/client_test.go
+++ b/internal/gitlabnet/healthcheck/client_test.go
@@ -6,8 +6,8 @@
 	"net/http"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 
 	"github.com/stretchr/testify/require"
 )
diff --git a/internal/gitlabnet/lfsauthenticate/client.go b/internal/gitlabnet/lfsauthenticate/client.go
--- a/internal/gitlabnet/lfsauthenticate/client.go
+++ b/internal/gitlabnet/lfsauthenticate/client.go
@@ -6,10 +6,10 @@
 	"net/http"
 	"strings"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/lfsauthenticate/client_test.go b/internal/gitlabnet/lfsauthenticate/client_test.go
--- a/internal/gitlabnet/lfsauthenticate/client_test.go
+++ b/internal/gitlabnet/lfsauthenticate/client_test.go
@@ -9,9 +9,9 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 const (
diff --git a/internal/gitlabnet/personalaccesstoken/client.go b/internal/gitlabnet/personalaccesstoken/client.go
--- a/internal/gitlabnet/personalaccesstoken/client.go
+++ b/internal/gitlabnet/personalaccesstoken/client.go
@@ -6,11 +6,11 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/personalaccesstoken/client_test.go b/internal/gitlabnet/personalaccesstoken/client_test.go
--- a/internal/gitlabnet/personalaccesstoken/client_test.go
+++ b/internal/gitlabnet/personalaccesstoken/client_test.go
@@ -8,11 +8,11 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 var (
diff --git a/internal/gitlabnet/twofactorrecover/client.go b/internal/gitlabnet/twofactorrecover/client.go
--- a/internal/gitlabnet/twofactorrecover/client.go
+++ b/internal/gitlabnet/twofactorrecover/client.go
@@ -6,11 +6,11 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/twofactorrecover/client_test.go b/internal/gitlabnet/twofactorrecover/client_test.go
--- a/internal/gitlabnet/twofactorrecover/client_test.go
+++ b/internal/gitlabnet/twofactorrecover/client_test.go
@@ -8,11 +8,11 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 var (
diff --git a/internal/gitlabnet/twofactorverify/client.go b/internal/gitlabnet/twofactorverify/client.go
--- a/internal/gitlabnet/twofactorverify/client.go
+++ b/internal/gitlabnet/twofactorverify/client.go
@@ -6,11 +6,11 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/twofactorverify/client_test.go b/internal/gitlabnet/twofactorverify/client_test.go
--- a/internal/gitlabnet/twofactorverify/client_test.go
+++ b/internal/gitlabnet/twofactorverify/client_test.go
@@ -7,13 +7,13 @@
 	"net/http"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func initialize(t *testing.T) []testserver.TestRequestHandler {
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -11,10 +11,10 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitaly"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/labkit/log"
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -12,10 +12,10 @@
 	grpcstatus "google.golang.org/grpc/status"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn) (int32, error) {
diff --git a/internal/keyline/key_line.go b/internal/keyline/key_line.go
--- a/internal/keyline/key_line.go
+++ b/internal/keyline/key_line.go
@@ -7,8 +7,8 @@
 	"regexp"
 	"strings"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
 )
 
 var (
diff --git a/internal/keyline/key_line_test.go b/internal/keyline/key_line_test.go
--- a/internal/keyline/key_line_test.go
+++ b/internal/keyline/key_line_test.go
@@ -4,7 +4,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func TestFailingNewPublicKeyLine(t *testing.T) {
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -9,7 +9,7 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func logFmt(inFmt string) string {
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -8,7 +8,7 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func TestConfigure(t *testing.T) {
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -13,10 +13,10 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/log"
 )
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -14,10 +14,10 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 type rejectCall struct {
@@ -223,6 +223,7 @@
 		{"disallowed command", disallowedcommand.Error},
 		{"not our ref", grpcstatus.Error(grpccodes.Internal, `rpc error: code = Internal desc = cmd wait: exit status 128, stderr: "fatal: git upload-pack: not our ref 9106d18f6a1b8022f6517f479696f3e3ea5e68c1"`)},
 	} {
+		ignoredError := ignoredError // Capture range variable, see "Race on loop counter" in https://go.dev/doc/articles/race_detector
 		t.Run(ignoredError.desc, func(t *testing.T) {
 			conn, chans = setup(1, newChannel)
 			conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -10,8 +10,8 @@
 
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/authorizedkeys"
 
 	"gitlab.com/gitlab-org/labkit/log"
 )
@@ -39,17 +39,14 @@
 type serverConfig struct {
 	cfg                  *config.Config
 	hostKeys             []ssh.Signer
+	hostKeyToCertMap     map[string]*ssh.Certificate
 	authorizedKeysClient *authorizedkeys.Client
 }
 
-func newServerConfig(cfg *config.Config) (*serverConfig, error) {
-	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
-	if err != nil {
-		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
-	}
+func parseHostKeys(keyFiles []string) []ssh.Signer {
+	var hostKeys []ssh.Signer
 
-	var hostKeys []ssh.Signer
-	for _, filename := range cfg.Server.HostKeyFiles {
+	for _, filename := range keyFiles {
 		keyRaw, err := os.ReadFile(filename)
 		if err != nil {
 			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("Failed to read host key")
@@ -63,11 +60,70 @@
 
 		hostKeys = append(hostKeys, key)
 	}
+
+	return hostKeys
+}
+
+func parseHostCerts(hostKeys []ssh.Signer, certFiles []string) map[string]*ssh.Certificate {
+	keyToCertMap := map[string]*ssh.Certificate{}
+	hostKeyIndex := make(map[string]int)
+
+	for index, hostKey := range hostKeys {
+		hostKeyIndex[string(hostKey.PublicKey().Marshal())] = index
+	}
+
+	for _, filename := range certFiles {
+		keyRaw, err := os.ReadFile(filename)
+		if err != nil {
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("failed to read host certificate")
+			continue
+		}
+		publicKey, _, _, _, err := ssh.ParseAuthorizedKey(keyRaw)
+		if err != nil {
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("failed to parse host certificate")
+			continue
+		}
+
+		cert, ok := publicKey.(*ssh.Certificate)
+		if !ok {
+			log.WithFields(log.Fields{"filename": filename}).Warn("failed to decode host certificate")
+			continue
+		}
+
+		hostRawKey := string(cert.Key.Marshal())
+		index, found := hostKeyIndex[hostRawKey]
+		if found {
+			keyToCertMap[hostRawKey] = cert
+
+			certSigner, err := ssh.NewCertSigner(cert, hostKeys[index])
+			if err != nil {
+				log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("the host certificate doesn't match the host private key")
+				continue
+			}
+
+			hostKeys[index] = certSigner
+		} else {
+			log.WithFields(log.Fields{"filename": filename}).Warnf("no matching private key for certificate %s", filename)
+		}
+	}
+
+	return keyToCertMap
+}
+
+func newServerConfig(cfg *config.Config) (*serverConfig, error) {
+	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+	}
+
+	hostKeys := parseHostKeys(cfg.Server.HostKeyFiles)
 	if len(hostKeys) == 0 {
 		return nil, fmt.Errorf("No host keys could be loaded, aborting")
 	}
 
-	return &serverConfig{cfg: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
+	hostKeyToCertMap := parseHostCerts(hostKeys, cfg.Server.HostCertFiles)
+
+	return &serverConfig{cfg: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys, hostKeyToCertMap: hostKeyToCertMap}, nil
 }
 
 func (s *serverConfig) getAuthKey(ctx context.Context, user string, key ssh.PublicKey) (*authorizedkeys.Response, error) {
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
--- a/internal/sshd/server_config_test.go
+++ b/internal/sshd/server_config_test.go
@@ -5,14 +5,15 @@
 	"crypto/dsa"
 	"crypto/rand"
 	"crypto/rsa"
+	"os"
 	"path"
 	"testing"
 
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 func TestNewServerConfigWithoutHosts(t *testing.T) {
@@ -22,6 +23,45 @@
 	require.Equal(t, "No host keys could be loaded, aborting", err.Error())
 }
 
+func TestHostKeyAndCerts(t *testing.T) {
+	testhelper.PrepareTestRootDir(t)
+
+	srvCfg := config.ServerConfig{
+		Listen:                  "127.0.0.1",
+		ConcurrentSessionsLimit: 1,
+		HostKeyFiles: []string{
+			path.Join(testhelper.TestRoot, "certs/valid/server.key"),
+		},
+		HostCertFiles: []string{
+			path.Join(testhelper.TestRoot, "certs/valid/server-cert.pub"),
+			path.Join(testhelper.TestRoot, "certs/valid/server2-cert.pub"),
+			path.Join(testhelper.TestRoot, "certs/invalid/server-cert.pub"),
+			path.Join(testhelper.TestRoot, "certs/invalid-path.key"),
+			path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+		},
+	}
+
+	cfg, err := newServerConfig(
+		&config.Config{GitlabUrl: "http://localhost", User: "user", Server: srvCfg},
+	)
+	require.NoError(t, err)
+
+	require.Len(t, cfg.hostKeys, 1)
+	require.Len(t, cfg.hostKeyToCertMap, 1)
+
+	// Check that the entry is pointing to the server's public key
+	data, err := os.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server.pub"))
+	require.NoError(t, err)
+
+	publicKey, _, _, _, err := ssh.ParseAuthorizedKey(data)
+	require.NoError(t, err)
+	require.NotNil(t, publicKey)
+	cert, ok := cfg.hostKeyToCertMap[string(publicKey.Marshal())]
+	require.True(t, ok)
+	require.NotNil(t, cert)
+	require.Equal(t, cert, cfg.hostKeys[0].PublicKey())
+}
+
 func TestFailedAuthorizedKeysClient(t *testing.T) {
 	_, err := newServerConfig(&config.Config{GitlabUrl: "ftp://localhost"})
 
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -12,13 +12,13 @@
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
-	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 type session struct {
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -11,9 +11,9 @@
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
 )
 
 type fakeChannel struct {
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -12,8 +12,10 @@
 	"github.com/pires/go-proxyproto"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/log"
@@ -145,13 +147,26 @@
 	return s.status
 }
 
+func contextWithValues(parent context.Context, nconn net.Conn) context.Context {
+	ctx := correlation.ContextWithCorrelation(parent, correlation.SafeRandomID())
+
+	// If we're dealing with a PROXY connection, register the original requester's IP
+	mconn, ok := nconn.(*proxyproto.Conn)
+	if ok {
+		ip := gitlabnet.ParseIP(mconn.Raw().RemoteAddr().String())
+		ctx = context.WithValue(ctx, client.OriginalRemoteIPContextKey{}, ip)
+	}
+
+	return ctx
+}
+
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
 	defer s.wg.Done()
 
 	metrics.SshdConnectionsInFlight.Inc()
 	defer metrics.SshdConnectionsInFlight.Dec()
 
-	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
+	ctx, cancel := context.WithCancel(contextWithValues(ctx, nconn))
 	defer cancel()
 	go func() {
 		<-ctx.Done()
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -15,9 +15,9 @@
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 const (
@@ -27,6 +27,7 @@
 
 var (
 	correlationId = ""
+	xForwardedFor = ""
 )
 
 func TestListenAndServe(t *testing.T) {
@@ -63,6 +64,10 @@
 		},
 		DestinationAddr: target,
 	}
+	xForwardedFor = "127.0.0.1"
+	defer func() {
+		xForwardedFor = "" // Cleanup for other test cases
+	}()
 
 	testCases := []struct {
 		desc        string
@@ -132,9 +137,9 @@
 				require.NoError(t, err)
 			}
 
-			sshConn, _, _, err := ssh.NewClientConn(conn, serverUrl, clientConfig(t))
+			sshConn, sshChans, sshRequs, err := ssh.NewClientConn(conn, serverUrl, clientConfig(t))
 			if sshConn != nil {
-				sshConn.Close()
+				defer sshConn.Close()
 			}
 
 			if tc.isRejected {
@@ -142,6 +147,10 @@
 				require.Regexp(t, "ssh: handshake failed", err.Error())
 			} else {
 				require.NoError(t, err)
+				client := ssh.NewClient(sshConn, sshChans, sshRequs)
+				defer client.Close()
+
+				holdSession(t, client)
 			}
 		})
 	}
@@ -306,6 +315,7 @@
 				correlationId = r.Header.Get("X-Request-Id")
 
 				require.NotEmpty(t, correlationId)
+				require.Equal(t, xForwardedFor, r.Header.Get("X-Forwarded-For"))
 
 				fmt.Fprint(w, `{"id": 1000, "key": "key"}`)
 			},
@@ -313,6 +323,7 @@
 			Path: "/api/v4/internal/discover",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				require.Equal(t, correlationId, r.Header.Get("X-Request-Id"))
+				require.Equal(t, xForwardedFor, r.Header.Get("X-Forwarded-For"))
 
 				fmt.Fprint(w, `{"id": 1000, "name": "Test User", "username": "test-user"}`)
 			},
diff --git a/internal/sshenv/sshenv_test.go b/internal/sshenv/sshenv_test.go
--- a/internal/sshenv/sshenv_test.go
+++ b/internal/sshenv/sshenv_test.go
@@ -4,7 +4,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 func TestNewFromEnv(t *testing.T) {
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -7,7 +7,7 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
 )
 
 func BuildDisallowedByApiHandlers(t *testing.T) []testserver.TestRequestHandler {
diff --git a/internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub b/internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub
@@ -0,0 +1,1 @@
+ssh-rsa-cert-v01@openssh.com AAAAB3NzaC1yc2EAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yql not_a_valid_cert.pub
diff --git a/internal/testhelper/testdata/testroot/certs/valid/ca b/internal/testhelper/testdata/testroot/certs/valid/ca
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/ca
@@ -0,0 +1,38 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAYEA3IUBN80/BJG5ut9U238FwIGAfMZQoOXYbFtiQZo+U+cdbGJlEGgV
++IToGN78YJypkbK8NiptMMOgWZ8eu6DCJMNxjPb7e3XYGrmHPeoK5Z9ioESDw0QSkxDIfw
+FoLSnx0+eiXS138ypENgLT/hOMl+fmroA74ZpvkvIpK+RoAo1qlWCWtA7NygkQkWRgnnAj
+4jkDKq9aT1iHGa9nQYLuTtfiX3l35/oOOMJFrDg7bb5EHnBsKewDoAxdqNbxp4W/sQyUF4
+NbOFLGzRavA6eNRfOx9UXcdy0v9+UgCmu92nbiPmNBo9wY3D9prwXAWetTM1B3ZCDk1hqI
+cXRg6pNBNC9e2Fubchs9TcrLaZpr4kgFvTekGcof1m2ozJTVlcIJlJpy95mp1uXwxTPbfX
+KGPvKu7NroR2avkAIfwRj5E/8HwrgDH2r/4uRPadRCx9hm38HI1n6S4YrrsmL9ffLW6SCf
+EG6c7+URDQr18Vq9YyxMmu09EK7VlDDX0JM0Uan5AAAFkA5fIlIOXyJSAAAAB3NzaC1yc2
+EAAAGBANyFATfNPwSRubrfVNt/BcCBgHzGUKDl2GxbYkGaPlPnHWxiZRBoFfiE6Bje/GCc
+qZGyvDYqbTDDoFmfHrugwiTDcYz2+3t12Bq5hz3qCuWfYqBEg8NEEpMQyH8BaC0p8dPnol
+0td/MqRDYC0/4TjJfn5q6AO+Gab5LyKSvkaAKNapVglrQOzcoJEJFkYJ5wI+I5AyqvWk9Y
+hxmvZ0GC7k7X4l95d+f6DjjCRaw4O22+RB5wbCnsA6AMXajW8aeFv7EMlBeDWzhSxs0Wrw
+OnjUXzsfVF3HctL/flIAprvdp24j5jQaPcGNw/aa8FwFnrUzNQd2Qg5NYaiHF0YOqTQTQv
+Xthbm3IbPU3Ky2maa+JIBb03pBnKH9ZtqMyU1ZXCCZSacveZqdbl8MUz231yhj7yruza6E
+dmr5ACH8EY+RP/B8K4Ax9q/+LkT2nUQsfYZt/ByNZ+kuGK67Ji/X3y1ukgnxBunO/lEQ0K
+9fFavWMsTJrtPRCu1ZQw19CTNFGp+QAAAAMBAAEAAAGACHnYRSPPc0aCpAsngNRODUss/B
+7HRJfxDKEqkqjyElmEyQCzL8FAbu/019fiTXhYEDCViWNyFPi/9hHmpYGVVMJqX+eyXNl3
+t/c/moKfbpoEuXJIuj2olRyFCFSug2XkVKfHlttDjAYo3waWzWJE+iXAuR5WruI3vacvK+
++4i7iRyzIOONeE02orx9ra19wplO1qEL7ysrANaVBToLH+pOspWVAa6sCywT2+XdM/fYVd
+qunZTncy4Hj5NJ8mZLEATfJKnT2v7C47fBjN+ylqpyTImBZxSfVyjrljcQXb9ExjAhVTjv
+tBuZdB1NPnok9cycwpg6aGXuZX2mSQWROhHM/r80kUzfxJpRDs/AqMWRZYC2k/kCKbXg7S
+1cuAwJ2SiH5jslekhbB8bCU3rL2SgUV4oZsqh5fb6ZsytXarbzX/8Kcmb4KGsjZ7wBD6Yu
+sJ05TkzC/HkOT3xTXwyzZpEldKucLClnY3Boq8pkO1EoUD8uPJNgSgukH9W5SleaIxAAAA
+wEzXR8Av4SxsgWW2pPVtQaeKhUKrid21RuPF5/7c4PZ4GlnhL3Fod4PwdD8yPjuIuI7s6/
+9HRxzi+vr616/BXMagGWPSZEQMaX6I/L5CSratN2Dk1jSYcH501GseILr+kIcZhe7HoEf2
+xbr8ByF88DXpeSdimIqMeVYTPGWac7oSf3Y5WHi9FUuJ4BEccu8bLIXWkGMK6yi/zJo1RQ
+u4aMzdMyzat0C2aeAm40HABdUv350K/H20Voj7zfhmlXvQ7wAAAMEA8a1oEPFL1+cAqfUD
+Jbx+KWyw/1kFBIpU93rk2qfJR593nMLebAk0l9qfhbvlN6GTNcGETjzGK1bHaD9G14c2IT
+bFcIoKmah6LygIlMGwdTMSWPPrczeIhMy6H0rJ2lDa208+nLwKqlFlMDYNpycL2Q1ZynnB
+fYqfRiUSDJcs+2jfTX0gA17NuSwqp6j/JlMm45tN3GK1neIVH+4PBazBXqZTzdfCfqJ9r5
+TWJw2i6CsSlCDAtO3uo+Pyj327RbNtAAAAwQDplpqK2+0+QiQB+LUeT0hfWp5/HmXJjfgm
+u+xIICJKPqOYwDvBWEHHssv7YS70dFJrbENJ66NZfdv+foDbQXrr10odbIntk9QoO4CS1g
+zd63kolFCLhbwkYos45CjJIPuzNDeiYIgsLEOQwnjHbp3HxAIywxtUPKj80YmfoogeidiD
+JNMwRoJfqlNziW1PDq0r8Zhw2lbyGZPI218ox7tsJ94BS4MFJfgASwO9qcDsaYz23sS8uQ
+BBbY6cCknC7T0AAAAUc3Rhbmh1QGpldC1hcm0ubG9jYWwBAgMEBQYH
+-----END OPENSSH PRIVATE KEY-----
diff --git a/internal/testhelper/testdata/testroot/certs/valid/ca.pub b/internal/testhelper/testdata/testroot/certs/valid/ca.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/ca.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDchQE3zT8Ekbm631TbfwXAgYB8xlCg5dhsW2JBmj5T5x1sYmUQaBX4hOgY3vxgnKmRsrw2Km0ww6BZnx67oMIkw3GM9vt7ddgauYc96grln2KgRIPDRBKTEMh/AWgtKfHT56JdLXfzKkQ2AtP+E4yX5+augDvhmm+S8ikr5GgCjWqVYJa0Ds3KCRCRZGCecCPiOQMqr1pPWIcZr2dBgu5O1+JfeXfn+g44wkWsODttvkQecGwp7AOgDF2o1vGnhb+xDJQXg1s4UsbNFq8Dp41F87H1Rdx3LS/35SAKa73aduI+Y0Gj3BjcP2mvBcBZ61MzUHdkIOTWGohxdGDqk0E0L17YW5tyGz1NystpmmviSAW9N6QZyh/WbajMlNWVwgmUmnL3manW5fDFM9t9coY+8q7s2uhHZq+QAh/BGPkT/wfCuAMfav/i5E9p1ELH2GbfwcjWfpLhiuuyYv198tbpIJ8Qbpzv5RENCvXxWr1jLEya7T0QrtWUMNfQkzRRqfk= test@test.example.org
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server-cert.pub b/internal/testhelper/testdata/testroot/certs/valid/server-cert.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server-cert.pub
@@ -0,0 +1,1 @@
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgNGUtD+qw0Xj5NU2uj4+4LoCWPcvXP54F9Adw/hWN5LAAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yqlAAAAAAAAAAAAAAACAAAABnNlcnZlcgAAAAAAAAAAAAAAAP//////////AAAAAAAAAAAAAAAAAAABlwAAAAdzc2gtcnNhAAAAAwEAAQAAAYEA3IUBN80/BJG5ut9U238FwIGAfMZQoOXYbFtiQZo+U+cdbGJlEGgV+IToGN78YJypkbK8NiptMMOgWZ8eu6DCJMNxjPb7e3XYGrmHPeoK5Z9ioESDw0QSkxDIfwFoLSnx0+eiXS138ypENgLT/hOMl+fmroA74ZpvkvIpK+RoAo1qlWCWtA7NygkQkWRgnnAj4jkDKq9aT1iHGa9nQYLuTtfiX3l35/oOOMJFrDg7bb5EHnBsKewDoAxdqNbxp4W/sQyUF4NbOFLGzRavA6eNRfOx9UXcdy0v9+UgCmu92nbiPmNBo9wY3D9prwXAWetTM1B3ZCDk1hqIcXRg6pNBNC9e2Fubchs9TcrLaZpr4kgFvTekGcof1m2ozJTVlcIJlJpy95mp1uXwxTPbfXKGPvKu7NroR2avkAIfwRj5E/8HwrgDH2r/4uRPadRCx9hm38HI1n6S4YrrsmL9ffLW6SCfEG6c7+URDQr18Vq9YyxMmu09EK7VlDDX0JM0Uan5AAABlAAAAAxyc2Etc2hhMi01MTIAAAGAapK5VzLDoRe88tnLORrH8VxLegTWPKGdn0k5Ye8tS5XUgd0N98gU669y4ErDNf0kPxlz40bjsfisEEtJ/N7m14IskCepScfZRh8w6QgPxbTOhmrc89xooqBUE50y5FU9hIIbzUrnEP+Dfu4IiFPToAguCa+KoAKiOX7lBQqEugV6uOWVZ2erPopEv+OiMLD8hXsuKKjQ+TomHZ/IjuXsFdXH7Vcl0VsPaAcxyCtDU7nCTJSTUoUZtMtwwpulp0e/zNmFrEn2Binz4jRaUlk3FM2fdbotviDQYeOY3npmxaWUvvQ/eKn0/DzUTAKAGr2LDa2XWnPhj51BS1XkNaUlnupdYmZ2Sok0R4U3bfVwokteREvAltGbXQSDtZwLS5NEY6vIdDrxpn5QRf9vGjqnc7piXxye9gcLne4YDUi24IhGyHrnWKCC0HjF7tuUhCOVKrqRdmHxRGWX3PlS8Xn6HHEPWU+YZnfT1V2W7LAFcDMozQbs4GPGzZdR3f3vCOJ8 server.pub
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server.pub b/internal/testhelper/testdata/testroot/certs/valid/server.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yql
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub b/internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub
@@ -0,0 +1,1 @@
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg3XO30wn4+lFxc5fLsanH4Pvu0OuKGHoR94zZjxx7qAEAAAADAQABAAABgQDK4MvpmiZ0cLS4+p3YEwcCwo4kVbJPTEeiIMuoEI7KJ3yqAjKJuns6VpnC6aRgu3LmuM4uBHcimQi415ClBOm4+tFiVsVNcAksA+QuU8rTEC6xPs96y1Y/qC+/WQn6+uKp1+fAspWsLpig3VXSTfq+YAcxZfTdO/6ck0kHVvxX096Ye7D0mdq2lWbGwSBlAbzU1wX/Znv5hLZD4DSG1cIjA9vQ/fJ9pwclZiS0qQ1VXdoIUAvL9tTAKj295VGT2NMGGYZQAQ0vXtM1YHOMAZ4XoPL1oVFjVEZoLRD58a6Dpe4hY8QKe6X1w7zU1rr5vVYrz7MTUa5pzsUSOeUTc3kyKJrExVkoLyBgYQq8qW9kYk/Ox9zHwTAKw9vwiIs2Mwe6nlxJ7cw4zykMsxPK4z9HkbZoTFWy3phuPFqs/WQoCvHTKNjWPab7UAameM6Dn0N83BF21tCTMWjkRCvtZVGIGZ7Y4cAiiN0OPzQWDasJ/IKVDRguZJRn3kgHCMYCgokAAAAAAAAAAAAAAAIAAAAHc2VydmVyMgAAAAAAAAAAAAAAAP//////////AAAAAAAAAAAAAAAAAAABlwAAAAdzc2gtcnNhAAAAAwEAAQAAAYEA3IUBN80/BJG5ut9U238FwIGAfMZQoOXYbFtiQZo+U+cdbGJlEGgV+IToGN78YJypkbK8NiptMMOgWZ8eu6DCJMNxjPb7e3XYGrmHPeoK5Z9ioESDw0QSkxDIfwFoLSnx0+eiXS138ypENgLT/hOMl+fmroA74ZpvkvIpK+RoAo1qlWCWtA7NygkQkWRgnnAj4jkDKq9aT1iHGa9nQYLuTtfiX3l35/oOOMJFrDg7bb5EHnBsKewDoAxdqNbxp4W/sQyUF4NbOFLGzRavA6eNRfOx9UXcdy0v9+UgCmu92nbiPmNBo9wY3D9prwXAWetTM1B3ZCDk1hqIcXRg6pNBNC9e2Fubchs9TcrLaZpr4kgFvTekGcof1m2ozJTVlcIJlJpy95mp1uXwxTPbfXKGPvKu7NroR2avkAIfwRj5E/8HwrgDH2r/4uRPadRCx9hm38HI1n6S4YrrsmL9ffLW6SCfEG6c7+URDQr18Vq9YyxMmu09EK7VlDDX0JM0Uan5AAABlAAAAAxyc2Etc2hhMi01MTIAAAGANOKG8Tq7kp9B5+CQEyb+mEatJOoQRV+4rpemWlfEw6TuVwQN2wSXc6XKBHzSG4NRnFkwk6GgiPLQEf4lBBKA8VYQnDKuhrHJlU4DFCRPw/aceHfCwNOruyJmuf91W3yEO/kYAd6EhkQiW/K3ky7BuXCqR34T2fBZSCeYhNcXWxhEMLoAuj0kEdX+YMNBmiPtinPE13KMFGyIVBm/ojgSZa8j4WnhDcK0cWv0OSGTgJF6q3hENCWRz2E1HroKUiABOy5Nca6gPVAi4OTd7gwER8eh9MngVHYorAJ3N9HjUh640SbL3zCC8f/lqIztqsHY0u3olsQ0gLXpFain+430HeyJlmVlsDZgQKRb90Mm1viSCKvHGpmVDYMimE9y0DCQS1i0yRGF1uSIPtuQ0NCbhS/HPKsT3nYgGCEuoB8aGOu3aGB/tmUkYXW+pwXRKqw0f/zX088XWYWvA+AR4hmmr6DDMnf/4EHgJp3xHTEwOBHCVj69xvlOawBNlL2X0b2p server2.pub
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server2.key b/internal/testhelper/testdata/testroot/certs/valid/server2.key
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server2.key
@@ -0,0 +1,38 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAYEAyuDL6ZomdHC0uPqd2BMHAsKOJFWyT0xHoiDLqBCOyid8qgIyibp7
+OlaZwumkYLty5rjOLgR3IpkIuNeQpQTpuPrRYlbFTXAJLAPkLlPK0xAusT7PestWP6gvv1
+kJ+vriqdfnwLKVrC6YoN1V0k36vmAHMWX03Tv+nJNJB1b8V9PemHuw9JnatpVmxsEgZQG8
+1NcF/2Z7+YS2Q+A0htXCIwPb0P3yfacHJWYktKkNVV3aCFALy/bUwCo9veVRk9jTBhmGUA
+ENL17TNWBzjAGeF6Dy9aFRY1RGaC0Q+fGug6XuIWPECnul9cO81Na6+b1WK8+zE1Guac7F
+EjnlE3N5MiiaxMVZKC8gYGEKvKlvZGJPzsfcx8EwCsPb8IiLNjMHup5cSe3MOM8pDLMTyu
+M/R5G2aExVst6YbjxarP1kKArx0yjY1j2m+1AGpnjOg59DfNwRdtbQkzFo5EQr7WVRiBme
+2OHAIojdDj80Fg2rCfyClQ0YLmSUZ95IBwjGAoKJAAAFkBOZmjETmZoxAAAAB3NzaC1yc2
+EAAAGBAMrgy+maJnRwtLj6ndgTBwLCjiRVsk9MR6Igy6gQjsonfKoCMom6ezpWmcLppGC7
+cua4zi4EdyKZCLjXkKUE6bj60WJWxU1wCSwD5C5TytMQLrE+z3rLVj+oL79ZCfr64qnX58
+CylawumKDdVdJN+r5gBzFl9N07/pyTSQdW/FfT3ph7sPSZ2raVZsbBIGUBvNTXBf9me/mE
+tkPgNIbVwiMD29D98n2nByVmJLSpDVVd2ghQC8v21MAqPb3lUZPY0wYZhlABDS9e0zVgc4
+wBnheg8vWhUWNURmgtEPnxroOl7iFjxAp7pfXDvNTWuvm9VivPsxNRrmnOxRI55RNzeTIo
+msTFWSgvIGBhCrypb2RiT87H3MfBMArD2/CIizYzB7qeXEntzDjPKQyzE8rjP0eRtmhMVb
+LemG48Wqz9ZCgK8dMo2NY9pvtQBqZ4zoOfQ3zcEXbW0JMxaOREK+1lUYgZntjhwCKI3Q4/
+NBYNqwn8gpUNGC5klGfeSAcIxgKCiQAAAAMBAAEAAAGAUxojzMt85wNns8HMuD6LB6FkEh
+QcVwka6plecrhdlQb5tLXzt6DwayQgFcwYrhr6ZPHcWtMvbbeb8AM017OcfU4YSJzccuzq
+hOIPLL7b/PrK9YWR/W2fJbIh5NJ3GRx9ji7HWpKMZpwrnvEq/1s705GIQL7Pv3Ocxsw6BM
+ynzt4VdwZrpLYE9fdawx1GxLkifViat1Rmgf3PnxwOyBB1Vlx1RTVQiBHMBpDBhlMdCBPK
+hM8tFd5EpXZoFgoCEXqlssIptaf0zUZAgeES31GwNrP7+n6SlL+xZxbs7ykWKjA1ibYDTE
+fLIojOQCOgnIFaDFbbgUiqxoYg1SAr2SRPOjopc5EXSt5kfCdQk3I5MKoSm2INNuwBqprI
+/BL0Do3VowAQkxjXJUWit9RR0wS7FiA54WJqOrfU+2ChRooVUvtt0i/0y9tJMr6+PJYawZ
+uLwQ4DXs3UNVFKdopyh+zcLht+1xIZO6VrMteXejVhcz8UnRQE7leCdOvCYbBX87eRAAAA
+wFX+Q0Cp8SD3Pf607nq0GNYgY+vQR/zqqTnIlYt3kKt0jP6TuvkOUnaANOt29W17LxIR1U
+tZxFfyktrT4/RiVRP+QWvdLS32IN3mpCPt+J0oKujlSWrUT0SJgd7ZLePJIOscjuFqW72M
+dNV7aCNIisc2QgJbp5EwCNTFxQmdqryk9Pd80kWSSAwWQ5A6jXSrLEAdaFTa9kF/o3DGNe
+E/6HOTnt7BFvdE1hetYFnUTR1vqjD21Pi3d5rnePYUOGI1nAAAAMEA9fDwdHOCjosfA1ri
+pWZDYYkR5JaxrCFZC5h2LcYL01aCutISH07Z5gmnAVM+CfHkihck9wiGDJuJNTo1mYR1pg
+aFgoIFg8LjZzBConlSnHTPYkIYHvYFaE9T4PIS8yjlHjaDn59P06nHRNa5W/4vvhGK8eEn
+hpBCQ68huqMZyCH9az6BYR1TasHY7GMbIuMXpswXt9tWRzXpuvQq0BFThNelviTw3JNFhC
+cdR2+2wMCErnT0w1Y9fd+8SIHL3OdVAAAAwQDTLPokbpKxlOQOdKMGgn0CZnhiAiLMKp6k
+ZQmqNjVEdiOkLWvmoBYMxW93n6uCpyc7p9R+++xXWvfIH7o4fCpiIcMKQtd7Ikp4s4uC6q
+xP0QOtGui5wac/iBgd0QB7ZlRh0WNIRS0ej5sn3wHz7es5eq1F/auGhxR6i5N0EhB/qI72
+lFgWtRJsIxi0tm424K6XWEjjj7fe7k9qQ712BVOvvhZp1OK/qfsOl6lj7VPXtcVPr9+6Aw
+MsHc/xbLoyRmUAAAAUc3Rhbmh1QGpldC1hcm0ubG9jYWwBAgMEBQYH
+-----END OPENSSH PRIVATE KEY-----
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server2.pub b/internal/testhelper/testdata/testroot/certs/valid/server2.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server2.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDK4MvpmiZ0cLS4+p3YEwcCwo4kVbJPTEeiIMuoEI7KJ3yqAjKJuns6VpnC6aRgu3LmuM4uBHcimQi415ClBOm4+tFiVsVNcAksA+QuU8rTEC6xPs96y1Y/qC+/WQn6+uKp1+fAspWsLpig3VXSTfq+YAcxZfTdO/6ck0kHVvxX096Ye7D0mdq2lWbGwSBlAbzU1wX/Znv5hLZD4DSG1cIjA9vQ/fJ9pwclZiS0qQ1VXdoIUAvL9tTAKj295VGT2NMGGYZQAQ0vXtM1YHOMAZ4XoPL1oVFjVEZoLRD58a6Dpe4hY8QKe6X1w7zU1rr5vVYrz7MTUa5pzsUSOeUTc3kyKJrExVkoLyBgYQq8qW9kYk/Ox9zHwTAKw9vwiIs2Mwe6nlxJ7cw4zykMsxPK4z9HkbZoTFWy3phuPFqs/WQoCvHTKNjWPab7UAameM6Dn0N83BF21tCTMWjkRCvtZVGIGZ7Y4cAiiN0OPzQWDasJ/IKVDRguZJRn3kgHCMYCgok=
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1668447678 -3600
#      Mon Nov 14 18:41:18 2022 +0100
# Branch heptapod
# Node ID e32995c877bf1786724c9937855ef18d0fdc30c7
# Parent  cf818cb4e4fe5440a953da19e2e8650bddbf45d5
Added tag heptapod-14.9.0 for changeset cf818cb4e4fe

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -50,3 +50,4 @@
 69c79e2a7c7cd53de504e610f85e0cc61bf184bd heptapod-14.6.0
 54e1067b740ecd4e2af0b52e4b2472366aeeb466 heptapod-14.7.0
 cfb031a1e9de24b88498850cff796c224ba48edd heptapod-14.7.1
+cf818cb4e4fe5440a953da19e2e8650bddbf45d5 heptapod-14.9.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1669027173 -3600
#      Mon Nov 21 11:39:33 2022 +0100
# Branch heptapod
# Node ID c6529456e762c07071757b58ff7e1cc87b6947da
# Parent  e32995c877bf1786724c9937855ef18d0fdc30c7
# Parent  45813b7245308037d79785402e6ab9a6b99fd3a2
Merged upstream v14.10.0 into Heptapod Shell

As usual, including changes in Heptapod version files.

In 3c492987c9aa, upstream fixed the race condition that we
previously had to fix in  cf818cb4e4fe.

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.10.0
+
+- Implement Push Auth support for 2FA verification !454
+
 v14.9.0
 
 - Update LabKit library to v1.16.0 !668
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 14.10.0
+
+- Bump to upstream GitLab Shell v14.10.0 (GitLab 15.3 and 15.4 series)
+
 ## 14.9.0
 
 - Bump to upstream GitLab Shell v14.9.0 (GitLab 15.2 series)
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-14.9.0
+14.10.0
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.9.0
+14.10.0
diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -4,6 +4,7 @@
 	"context"
 	"fmt"
 	"io"
+	"time"
 
 	"gitlab.com/gitlab-org/labkit/log"
 
@@ -13,6 +14,11 @@
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
 )
 
+const (
+	timeout = 30 * time.Second
+	prompt  = "OTP: "
+)
+
 type Command struct {
 	Config     *config.Config
 	Args       *commandargs.Shell
@@ -20,25 +26,51 @@
 }
 
 func (c *Command) Execute(ctx context.Context) error {
-	ctxlog := log.ContextLogger(ctx)
-	ctxlog.Info("twofactorverify: execute: waiting for user input")
-	otp := c.getOTP(ctx)
-
-	ctxlog.Info("twofactorverify: execute: verifying entered OTP")
-	err := c.verifyOTP(ctx, otp)
+	client, err := twofactorverify.NewClient(c.Config)
 	if err != nil {
-		ctxlog.WithError(err).Error("twofactorverify: execute: OTP verification failed")
 		return err
 	}
 
-	ctxlog.WithError(err).Info("twofactorverify: execute: OTP verified")
+	ctx, cancel := context.WithTimeout(ctx, timeout)
+	defer cancel()
+
+	fmt.Fprint(c.ReadWriter.Out, prompt)
+
+	resultCh := make(chan string)
+	go func() {
+		err := client.PushAuth(ctx, c.Args)
+		if err == nil {
+			resultCh <- "OTP has been validated by Push Authentication. Git operations are now allowed."
+		}
+	}()
+
+	go func() {
+		answer, err := c.getOTP(ctx)
+		if err != nil {
+			resultCh <- formatErr(err)
+		}
+
+		if err := client.VerifyOTP(ctx, c.Args, answer); err != nil {
+			resultCh <- formatErr(err)
+		} else {
+			resultCh <- "OTP validation successful. Git operations are now allowed."
+		}
+	}()
+
+	var message string
+	select {
+	case message = <-resultCh:
+	case <-ctx.Done():
+		message = formatErr(ctx.Err())
+	}
+
+	log.WithContextFields(ctx, log.Fields{"message": message}).Info("Two factor verify command finished")
+	fmt.Fprintf(c.ReadWriter.Out, "\n%v\n", message)
+
 	return nil
 }
 
-func (c *Command) getOTP(ctx context.Context) string {
-	prompt := "OTP: "
-	fmt.Fprint(c.ReadWriter.Out, prompt)
-
+func (c *Command) getOTP(ctx context.Context) (string, error) {
 	var answer string
 	otpLength := int64(64)
 	reader := io.LimitReader(c.ReadWriter.In, otpLength)
@@ -46,21 +78,13 @@
 		log.ContextLogger(ctx).WithError(err).Debug("twofactorverify: getOTP: Failed to get user input")
 	}
 
-	return answer
-}
-
-func (c *Command) verifyOTP(ctx context.Context, otp string) error {
-	client, err := twofactorverify.NewClient(c.Config)
-	if err != nil {
-		return err
+	if answer == "" {
+		return "", fmt.Errorf("OTP cannot be blank.")
 	}
 
-	err = client.VerifyOTP(ctx, c.Args, otp)
-	if err == nil {
-		fmt.Fprint(c.ReadWriter.Out, "\nOTP validation successful. Git operations are now allowed.\n")
-	} else {
-		fmt.Fprintf(c.ReadWriter.Out, "\nOTP validation failed.\n%v\n", err)
-	}
+	return answer, nil
+}
 
-	return nil
+func formatErr(err error) string {
+	return fmt.Sprintf("OTP validation failed: %v", err)
 }
diff --git a/internal/command/twofactorverify/twofactorverify_test.go b/internal/command/twofactorverify/twofactorverify_test.go
--- a/internal/command/twofactorverify/twofactorverify_test.go
+++ b/internal/command/twofactorverify/twofactorverify_test.go
@@ -17,10 +17,20 @@
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
 )
 
+type blockingReader struct{}
+
+func (*blockingReader) Read([]byte) (int, error) {
+	waitInfinitely := make(chan struct{})
+	<-waitInfinitely
+
+	return 0, nil
+}
+
 func setup(t *testing.T) []testserver.TestRequestHandler {
+	waitInfinitely := make(chan struct{})
 	requests := []testserver.TestRequestHandler{
 		{
-			Path: "/api/v4/internal/two_factor_otp_check",
+			Path: "/api/v4/internal/two_factor_manual_otp_check",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
@@ -31,11 +41,13 @@
 				require.NoError(t, json.Unmarshal(b, &requestBody))
 
 				switch requestBody.KeyId {
-				case "1":
+				case "verify_via_otp", "verify_via_otp_with_push_error":
 					body := map[string]interface{}{
 						"success": true,
 					}
 					json.NewEncoder(w).Encode(body)
+				case "wait_infinitely":
+					<-waitInfinitely
 				case "error":
 					body := map[string]interface{}{
 						"success": false,
@@ -47,15 +59,36 @@
 				}
 			},
 		},
+		{
+			Path: "/api/v4/internal/two_factor_push_otp_check",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				b, err := io.ReadAll(r.Body)
+				defer r.Body.Close()
+
+				require.NoError(t, err)
+
+				var requestBody *twofactorverify.RequestBody
+				require.NoError(t, json.Unmarshal(b, &requestBody))
+
+				switch requestBody.KeyId {
+				case "verify_via_push":
+					body := map[string]interface{}{
+						"success": true,
+					}
+					json.NewEncoder(w).Encode(body)
+				case "verify_via_otp_with_push_error":
+					w.WriteHeader(http.StatusInternalServerError)
+				default:
+					<-waitInfinitely
+				}
+			},
+		},
 	}
 
 	return requests
 }
 
-const (
-	question    = "OTP: \n"
-	errorHeader = "OTP validation failed.\n"
-)
+const errorHeader = "OTP validation failed: "
 
 func TestExecute(t *testing.T) {
 	requests := setup(t)
@@ -65,46 +98,61 @@
 	testCases := []struct {
 		desc           string
 		arguments      *commandargs.Shell
-		answer         string
+		input          io.Reader
 		expectedOutput string
 	}{
 		{
-			desc:      "With a known key id",
-			arguments: &commandargs.Shell{GitlabKeyId: "1"},
-			answer:    "123456\n",
-			expectedOutput: question +
-				"OTP validation successful. Git operations are now allowed.\n",
+			desc:           "Verify via OTP",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_otp"},
+			expectedOutput: "OTP validation successful. Git operations are now allowed.\n",
+		},
+		{
+			desc:           "Verify via OTP",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_otp_with_push_error"},
+			expectedOutput: "OTP validation successful. Git operations are now allowed.\n",
+		},
+		{
+			desc:           "Verify via push authentication",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_push"},
+			input:          &blockingReader{},
+			expectedOutput: "OTP has been validated by Push Authentication. Git operations are now allowed.\n",
+		},
+		{
+			desc:           "With an empty OTP",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_otp"},
+			input:          bytes.NewBufferString("\n"),
+			expectedOutput: errorHeader + "OTP cannot be blank.\n",
 		},
 		{
 			desc:           "With bad response",
 			arguments:      &commandargs.Shell{GitlabKeyId: "-1"},
-			answer:         "123456\n",
-			expectedOutput: question + errorHeader + "Parsing failed\n",
+			expectedOutput: errorHeader + "Parsing failed\n",
 		},
 		{
 			desc:           "With API returns an error",
 			arguments:      &commandargs.Shell{GitlabKeyId: "error"},
-			answer:         "yes\n",
-			expectedOutput: question + errorHeader + "error message\n",
+			expectedOutput: errorHeader + "error message\n",
 		},
 		{
 			desc:           "With API fails",
 			arguments:      &commandargs.Shell{GitlabKeyId: "broken"},
-			answer:         "yes\n",
-			expectedOutput: question + errorHeader + "Internal API error (500)\n",
+			expectedOutput: errorHeader + "Internal API error (500)\n",
 		},
 		{
 			desc:           "With missing arguments",
 			arguments:      &commandargs.Shell{},
-			answer:         "yes\n",
-			expectedOutput: question + errorHeader + "who='' is invalid\n",
+			expectedOutput: errorHeader + "who='' is invalid\n",
 		},
 	}
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
 			output := &bytes.Buffer{}
-			input := bytes.NewBufferString(tc.answer)
+
+			input := tc.input
+			if input == nil {
+				input = bytes.NewBufferString("123456\n")
+			}
 
 			cmd := &Command{
 				Config:     &config.Config{GitlabUrl: url},
@@ -115,7 +163,29 @@
 			err := cmd.Execute(context.Background())
 
 			require.NoError(t, err)
-			require.Equal(t, tc.expectedOutput, output.String())
+			require.Equal(t, prompt+"\n"+tc.expectedOutput, output.String())
 		})
 	}
 }
+
+func TestCanceledContext(t *testing.T) {
+	requests := setup(t)
+
+	output := &bytes.Buffer{}
+
+	url := testserver.StartSocketHttpServer(t, requests)
+	cmd := &Command{
+		Config:     &config.Config{GitlabUrl: url},
+		Args:       &commandargs.Shell{GitlabKeyId: "wait_infinitely"},
+		ReadWriter: &readwriter.ReadWriter{Out: output, In: &bytes.Buffer{}},
+	}
+
+	ctx, cancel := context.WithCancel(context.Background())
+
+	errCh := make(chan error)
+	go func() { errCh <- cmd.Execute(ctx) }()
+	cancel()
+
+	require.NoError(t, <-errCh)
+	require.Equal(t, prompt+"\n"+errorHeader+"context canceled\n", output.String())
+}
diff --git a/internal/gitlabnet/twofactorverify/client.go b/internal/gitlabnet/twofactorverify/client.go
--- a/internal/gitlabnet/twofactorverify/client.go
+++ b/internal/gitlabnet/twofactorverify/client.go
@@ -26,7 +26,7 @@
 type RequestBody struct {
 	KeyId      string `json:"key_id,omitempty"`
 	UserId     int64  `json:"user_id,omitempty"`
-	OTPAttempt string `json:"otp_attempt"`
+	OTPAttempt string `json:"otp_attempt,omitempty"`
 }
 
 func NewClient(config *config.Config) (*Client, error) {
@@ -44,7 +44,22 @@
 		return err
 	}
 
-	response, err := c.client.Post(ctx, "/two_factor_otp_check", requestBody)
+	response, err := c.client.Post(ctx, "/two_factor_manual_otp_check", requestBody)
+	if err != nil {
+		return err
+	}
+	defer response.Body.Close()
+
+	return parse(response)
+}
+
+func (c *Client) PushAuth(ctx context.Context, args *commandargs.Shell) error {
+	requestBody, err := c.getRequestBody(ctx, args, "")
+	if err != nil {
+		return err
+	}
+
+	response, err := c.client.Post(ctx, "/two_factor_push_otp_check", requestBody)
 	if err != nil {
 		return err
 	}
diff --git a/internal/gitlabnet/twofactorverify/client_test.go b/internal/gitlabnet/twofactorverify/client_test.go
--- a/internal/gitlabnet/twofactorverify/client_test.go
+++ b/internal/gitlabnet/twofactorverify/client_test.go
@@ -17,49 +17,55 @@
 )
 
 func initialize(t *testing.T) []testserver.TestRequestHandler {
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		b, err := io.ReadAll(r.Body)
+		defer r.Body.Close()
+
+		require.NoError(t, err)
+
+		var requestBody *RequestBody
+		require.NoError(t, json.Unmarshal(b, &requestBody))
+
+		switch requestBody.KeyId {
+		case "0":
+			body := map[string]interface{}{
+				"success": true,
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		case "1":
+			body := map[string]interface{}{
+				"success": false,
+				"message": "error message",
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		case "2":
+			w.WriteHeader(http.StatusForbidden)
+			body := &client.ErrorResponse{
+				Message: "Not allowed!",
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		case "3":
+			w.Write([]byte("{ \"message\": \"broken json!\""))
+		case "4":
+			w.WriteHeader(http.StatusForbidden)
+		}
+
+		if requestBody.UserId == 1 {
+			body := map[string]interface{}{
+				"success": true,
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		}
+	}
+
 	requests := []testserver.TestRequestHandler{
 		{
-			Path: "/api/v4/internal/two_factor_otp_check",
-			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := io.ReadAll(r.Body)
-				defer r.Body.Close()
-
-				require.NoError(t, err)
-
-				var requestBody *RequestBody
-				require.NoError(t, json.Unmarshal(b, &requestBody))
-
-				switch requestBody.KeyId {
-				case "0":
-					body := map[string]interface{}{
-						"success": true,
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "1":
-					body := map[string]interface{}{
-						"success": false,
-						"message": "error message",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "2":
-					w.WriteHeader(http.StatusForbidden)
-					body := &client.ErrorResponse{
-						Message: "Not allowed!",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "3":
-					w.Write([]byte("{ \"message\": \"broken json!\""))
-				case "4":
-					w.WriteHeader(http.StatusForbidden)
-				}
-
-				if requestBody.UserId == 1 {
-					body := map[string]interface{}{
-						"success": true,
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				}
-			},
+			Path:    "/api/v4/internal/two_factor_manual_otp_check",
+			Handler: handler,
+		},
+		{
+			Path:    "/api/v4/internal/two_factor_push_otp_check",
+			Handler: handler,
 		},
 		{
 			Path: "/api/v4/internal/discover",
@@ -140,6 +146,57 @@
 	}
 }
 
+func TestVerifyPush(t *testing.T) {
+	client := setup(t)
+
+	args := &commandargs.Shell{GitlabKeyId: "0"}
+	err := client.PushAuth(context.Background(), args)
+	require.NoError(t, err)
+}
+
+func TestErrorMessagePush(t *testing.T) {
+	client := setup(t)
+
+	args := &commandargs.Shell{GitlabKeyId: "1"}
+	err := client.PushAuth(context.Background(), args)
+	require.Equal(t, "error message", err.Error())
+}
+
+func TestErrorResponsesPush(t *testing.T) {
+	client := setup(t)
+
+	testCases := []struct {
+		desc          string
+		fakeId        string
+		expectedError string
+	}{
+		{
+			desc:          "A response with an error message",
+			fakeId:        "2",
+			expectedError: "Not allowed!",
+		},
+		{
+			desc:          "A response with bad JSON",
+			fakeId:        "3",
+			expectedError: "Parsing failed",
+		},
+		{
+			desc:          "An error response without message",
+			fakeId:        "4",
+			expectedError: "Internal API error (403)",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			args := &commandargs.Shell{GitlabKeyId: tc.fakeId}
+			err := client.PushAuth(context.Background(), args)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
+
 func setup(t *testing.T) *Client {
 	requests := initialize(t)
 	url := testserver.StartSocketHttpServer(t, requests)
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -223,12 +223,12 @@
 		{"disallowed command", disallowedcommand.Error},
 		{"not our ref", grpcstatus.Error(grpccodes.Internal, `rpc error: code = Internal desc = cmd wait: exit status 128, stderr: "fatal: git upload-pack: not our ref 9106d18f6a1b8022f6517f479696f3e3ea5e68c1"`)},
 	} {
-		ignoredError := ignoredError // Capture range variable, see "Race on loop counter" in https://go.dev/doc/articles/race_detector
 		t.Run(ignoredError.desc, func(t *testing.T) {
 			conn, chans = setup(1, newChannel)
+			ignored := ignoredError.err
 			conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 				close(chans)
-				return ignoredError.err
+				return ignored
 			})
 
 			require.InDelta(t, initialSessionsTotal+2+float64(i), testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
diff --git a/spec/gitlab_shell_two_factor_verify_spec.rb b/spec/gitlab_shell_two_factor_verify_spec.rb
--- a/spec/gitlab_shell_two_factor_verify_spec.rb
+++ b/spec/gitlab_shell_two_factor_verify_spec.rb
@@ -11,71 +11,115 @@
       'SSH_ORIGINAL_COMMAND' => '2fa_verify' }
   end
 
+  let(:correct_otp) { '123456' }
+
   before(:context) do
     write_config('gitlab_url' => "http+unix://#{CGI.escape(tmp_socket_path)}")
   end
 
   def mock_server(server)
-    server.mount_proc('/api/v4/internal/two_factor_otp_check') do |req, res|
+    server.mount_proc('/api/v4/internal/two_factor_manual_otp_check') do |req, res|
+      res.content_type = 'application/json'
+      res.status = 200
+
+      params = JSON.parse(req.body)
+
+      res.body = if params['otp_attempt'] == correct_otp
+                   { success: true }.to_json
+                 else
+                   { success: false, message: 'boom!' }.to_json
+                 end
+    end
+
+    server.mount_proc('/api/v4/internal/two_factor_push_otp_check') do |req, res|
       res.content_type = 'application/json'
       res.status = 200
 
       params = JSON.parse(req.body)
-      key_id = params['key_id'] || params['user_id'].to_s
+      id = params['key_id'] || params['user_id'].to_s
 
-      if key_id == '100'
+      if id == '100'
+        res.body = { success: false, message: 'boom!' }.to_json
+      else
         res.body = { success: true }.to_json
-      else
-        res.body = { success: false, message: 'boom!' }.to_json
       end
     end
 
-    server.mount_proc('/api/v4/internal/discover') do |_, res|
+    server.mount_proc('/api/v4/internal/discover') do |req, res|
       res.status = 200
       res.content_type = 'application/json'
-      res.body = { id: 100, name: 'Some User', username: 'someuser' }.to_json
+
+      if req.query['username'] == 'someone'
+        res.body = { id: 100, name: 'Some User', username: 'someuser' }.to_json
+      else
+        res.body = { id: 101, name: 'Another User', username: 'another' }.to_json
+      end
     end
   end
 
-  describe 'command' do
+  describe 'entering OTP manually' do
+    let(:cmd) { "#{gitlab_shell_path} key-100" }
+
     context 'when key is provided' do
-      let(:cmd) { "#{gitlab_shell_path} key-100" }
-
-      it 'prints a successful verification message' do
-        verify_successful_verification!(cmd)
+      it 'asks a user for a correct OTP' do
+        verify_successful_otp_verification!(cmd)
       end
     end
 
     context 'when username is provided' do
       let(:cmd) { "#{gitlab_shell_path} username-someone" }
 
-      it 'prints a successful verification message' do
-        verify_successful_verification!(cmd)
+      it 'asks a user for a correct OTP' do
+        verify_successful_otp_verification!(cmd)
       end
     end
 
-    context 'when API error occurs' do
-      let(:cmd) { "#{gitlab_shell_path} key-101" }
+    it 'shows an error when an invalid otp is provided' do
+      Open3.popen2(env, cmd) do |stdin, stdout|
+        asks_for_otp(stdout)
+        stdin.puts('000000')
 
-      it 'prints the error message' do
-        Open3.popen2(env, cmd) do |stdin, stdout|
-          expect(stdout.gets(5)).to eq('OTP: ')
-
-          stdin.puts('123456')
-
-          expect(stdout.flush.read).to eq("\nOTP validation failed.\nboom!\n")
-        end
+        expect(stdout.flush.read).to eq("\nOTP validation failed: boom!\n")
       end
     end
   end
 
-  def verify_successful_verification!(cmd)
+  describe 'authorizing via push' do
+    context 'when key is provided' do
+      let(:cmd) { "#{gitlab_shell_path} key-101" }
+
+      it 'asks a user for a correct OTP' do
+        verify_successful_push_verification!(cmd)
+      end
+    end
+
+    context 'when username is provided' do
+      let(:cmd) { "#{gitlab_shell_path} username-another" }
+
+      it 'asks a user for a correct OTP' do
+        verify_successful_push_verification!(cmd)
+      end
+    end
+  end
+
+  def verify_successful_otp_verification!(cmd)
     Open3.popen2(env, cmd) do |stdin, stdout|
-      expect(stdout.gets(5)).to eq('OTP: ')
-
-      stdin.puts('123456')
+      asks_for_otp(stdout)
+      stdin.puts(correct_otp)
 
       expect(stdout.flush.read).to eq("\nOTP validation successful. Git operations are now allowed.\n")
     end
   end
+
+  def verify_successful_push_verification!(cmd)
+    Open3.popen2(env, cmd) do |stdin, stdout|
+      asks_for_otp(stdout)
+
+      expect(stdout.flush.read).to eq("\nOTP has been validated by Push Authentication. Git operations are now allowed.\n")
+    end
+  end
+
+  def asks_for_otp(stdout)
+    expect(stdout.gets(5)).to eq('OTP: ')
+  end
 end
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1669113408 -3600
#      Tue Nov 22 11:36:48 2022 +0100
# Branch heptapod-stable
# Node ID f8f9fec42f926a6cee1f1b54a90d371e96c44d77
# Parent  0b31292ecc0575dfcd23ee86046834b5a68cdd0d
# Parent  e32995c877bf1786724c9937855ef18d0fdc30c7
heptapod#711: making 0.34 the new stable

diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,4 @@
 tags
 tmp/*
 vendor
+.DS_Store
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,7 +8,8 @@
       - '/ci/danger-review.yml'
 
 variables:
-  DOCKER_VERSION: "20.10.3"
+  DOCKER_VERSION: "20.10.15"
+  BUNDLE_FROZEN: "true"
 
 workflow:
   rules: &workflow_rules
diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -44,3 +44,10 @@
 98d781e90b1bb472b9c37ad5fbdbed541e1457c5 heptapod-14.2.0
 7e62e4be19def9c1f1773e326480a9429210bb8d heptapod-14.3.0
 fca0cc645e5bbe898f18befe0f31f51f091db81b heptapod-14.3.1
+61f29699614da28ec795cec912a75f398df84af3 heptapod-14.3.2
+8696661a67a6e7816fca8066dcbdc66b261feb5b heptapod-14.4.0
+aaebb02398004cb60c56d98824084bf9a2c17073 heptapod-14.5.0
+69c79e2a7c7cd53de504e610f85e0cc61bf184bd heptapod-14.6.0
+54e1067b740ecd4e2af0b52e4b2472366aeeb466 heptapod-14.7.0
+cfb031a1e9de24b88498850cff796c224ba48edd heptapod-14.7.1
+cf818cb4e4fe5440a953da19e2e8650bddbf45d5 heptapod-14.9.0
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,62 @@
+v14.9.0
+
+- Update LabKit library to v1.16.0 !668
+
+v14.8.0
+
+- go: Bump major version to v14 !666
+- Pass original IP from PROXY requests to internal API calls !665
+- Fix make install copying the wrong binaries !664
+- gitlab-sshd: Add support for configuring host certificates !661
+
+v14.7.4
+
+- gitlab-sshd: Update crypto module to fix RSA keys with old gpg-agent !662
+
+v14.7.3
+
+- Ignore "not our ref" errors from gitlab-sshd error metrics !656
+
+v14.7.2
+
+- Exclude disallowed command from error rate !654
+
+v14.7.1
+
+- Log gitlab-sshd session level indicator errors !650
+- Improve establish session duration metrics !651
+
+v14.7.0
+
+- Abort long-running unauthenticated SSH connections !647
+- Close the connection when context is canceled !646
+
+v14.6.1
+
+- Return support for diffie-hellman-group14-sha1 !644
+
+v14.6.0
+
+- Exclude Gitaly unavailable error from error rate !641
+- Downgrade auth EOF messages from warning to debug !641
+- Display constistently in gitlab-sshd and gitlab-shell !641
+- Downgrade host key mismatch messages from warning to debug !639
+- Introduce a GitLab-SSHD server version during handshake !640
+- Narrow supported kex algorithms !638
+
+v14.5.0
+
+- Make ProxyHeaderTimeout configurable !635
+
+v14.4.0
+
+- Allow configuring SSH server algorithms !633
+- Update gitlab-org/golang-crypto module version !632
+
+v14.3.1
+
+- Exclude API errors from error rate !630
+
 v14.3.0
 
 - Remove deprecated bundler-audit !626
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -113,4 +113,4 @@
   rspec (~> 3.8.0)
 
 BUNDLED WITH
-   2.3.6
+   2.3.15
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,34 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 14.9.0
+
+- Bump to upstream GitLab Shell v14.9.0 (GitLab 15.2 series)
+
+## 14.7.1
+
+- Bump to upstream GitLab Shell v14.7.4 (GitLab 15.1 series)
+
+## 14.7.0
+
+- Bump to upstream GitLab Shell v14.7.0
+
+## 14.6.0
+
+- Bump to upstream GitLab Shell v14.6.0
+
+## 14.5.0
+
+- Bump to upstream GitLab Shell v14.5.0
+
+## 14.4.0
+
+- Bump to upstream GitLab Shell v14.4.0
+
+## 14.3.2
+
+- Bump to upstream GitLab Shell v14.3.1
+
 ## 14.3.1
 
 - Filled in `HEPTAPOD_VERSION` (had been forgotten in 14.0 through
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-14.3.1
+14.9.0
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -66,7 +66,6 @@
 	mkdir -p $(DESTDIR)$(PREFIX)/bin/
 	install -m755 bin/check $(DESTDIR)$(PREFIX)/bin/check
 	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell
-	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-keys-check
-	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-principals-check
-	install -m755 bin/gitlab-shell $(DESTDIR)$(PREFIX)/bin/gitlab-sshd
-
+	install -m755 bin/gitlab-shell-authorized-keys-check $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-keys-check
+	install -m755 bin/gitlab-shell-authorized-principals-check $(DESTDIR)$(PREFIX)/bin/gitlab-shell-authorized-principals-check
+	install -m755 bin/gitlab-sshd $(DESTDIR)$(PREFIX)/bin/gitlab-sshd
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -58,6 +58,44 @@
 
 There is also a rate-limiter in place in Gitaly, but the calls will never be made to Gitaly if the rate limit is exceeded in Gitlab Shell (Rails).
 
+## GitLab SaaS
+
+A diagram of the flow of `gitlab-shell` on GitLab.com:
+
+```mermaid
+graph LR
+    a2 --> b2
+    a2  --> b3
+    a2 --> b4
+    b2 --> c1
+    b3 --> c1
+    b4 --> c1
+    c2 --> d1
+    c2 --> d2
+    c2 --> d3
+    d1 --> e1
+    d2 --> e1
+    d3 --> e1
+    a1[Cloudflare] --> a2[TCP<br/> load balancer]
+    e1[Git]
+
+    subgraph HAProxy Fleet
+    b2[HAProxy]
+    b3[HAProxy]
+    b4[HAProxy]
+    end
+
+    subgraph GKE
+    c1[Internal TCP<br/> load balancer<br/>port 2222] --> c2[GitLab-shell<br/> pods]
+    end
+
+    subgraph Gitaly
+    d1[Gitaly]
+    d2[Gitaly]
+    d3[Gitaly]
+    end
+```
+
 ## Releasing
 
 See [PROCESS.md](./PROCESS.md)
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.3.0
+14.9.0
diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -15,8 +15,8 @@
 	"github.com/golang-jwt/jwt/v4"
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 var (
@@ -76,6 +76,7 @@
 			testErrorMessage(t, client)
 			testAuthenticationHeader(t, client)
 			testJWTAuthenticationHeader(t, client)
+			testXForwardedForHeader(t, client)
 		})
 	}
 }
@@ -221,6 +222,21 @@
 	})
 }
 
+func testXForwardedForHeader(t *testing.T, client *GitlabNetClient) {
+	t.Run("X-Forwarded-For Header inserted if original address in context", func(t *testing.T) {
+		ctx := context.WithValue(context.Background(), OriginalRemoteIPContextKey{}, "196.7.0.238")
+		response, err := client.Get(ctx, "/x_forwarded_for")
+		require.NoError(t, err)
+		require.NotNil(t, response)
+
+		defer response.Body.Close()
+
+		responseBody, err := io.ReadAll(response.Body)
+		require.NoError(t, err)
+		require.Equal(t, "196.7.0.238", string(responseBody))
+	})
+}
+
 func buildRequests(t *testing.T, relativeURLRoot string) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
@@ -257,6 +273,12 @@
 			},
 		},
 		{
+			Path: "/api/v4/internal/x_forwarded_for",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				fmt.Fprint(w, r.Header.Get("X-Forwarded-For"))
+			},
+		},
+		{
 			Path: "/api/v4/internal/error",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -37,6 +37,17 @@
 	userAgent  string
 }
 
+type ApiError struct {
+	Msg string
+}
+
+// To use as the key in a Context to set an X-Forwarded-For header in a request
+type OriginalRemoteIPContextKey struct{}
+
+func (e *ApiError) Error() string {
+	return e.Msg
+}
+
 func NewGitlabNetClient(
 	user,
 	password,
@@ -101,9 +112,9 @@
 	parsedResponse := &ErrorResponse{}
 
 	if err := json.NewDecoder(resp.Body).Decode(parsedResponse); err != nil {
-		return fmt.Errorf("Internal API error (%v)", resp.StatusCode)
+		return &ApiError{fmt.Sprintf("Internal API error (%v)", resp.StatusCode)}
 	} else {
-		return fmt.Errorf(parsedResponse.Message)
+		return &ApiError{parsedResponse.Message}
 	}
 
 }
@@ -142,6 +153,11 @@
 	}
 	request.Header.Set(apiSecretHeaderName, tokenString)
 
+	originalRemoteIP, ok := ctx.Value(OriginalRemoteIPContextKey{}).(string)
+	if ok {
+		request.Header.Add("X-Forwarded-For", originalRemoteIP)
+	}
+
 	request.Header.Add("Content-Type", "application/json")
 	request.Header.Add("User-Agent", c.userAgent)
 	request.Close = true
@@ -157,7 +173,7 @@
 
 	if err != nil {
 		logger.WithError(err).Error("Internal API unreachable")
-		return nil, fmt.Errorf("Internal API unreachable")
+		return nil, &ApiError{"Internal API unreachable"}
 	}
 
 	if response != nil {
diff --git a/client/httpclient_test.go b/client/httpclient_test.go
--- a/client/httpclient_test.go
+++ b/client/httpclient_test.go
@@ -11,7 +11,7 @@
 	"time"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
 )
 
 func TestReadTimeout(t *testing.T) {
diff --git a/client/httpsclient_test.go b/client/httpsclient_test.go
--- a/client/httpsclient_test.go
+++ b/client/httpsclient_test.go
@@ -9,8 +9,8 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 //go:generate openssl req -newkey rsa:4096 -new -nodes -x509 -days 3650 -out ../internal/testhelper/testdata/testroot/certs/client/server.crt -keyout ../internal/testhelper/testdata/testroot/certs/client/key.pem -subj "/C=US/ST=California/L=San Francisco/O=GitLab/OU=GitLab-Shell/CN=localhost"
diff --git a/client/testserver/testserver.go b/client/testserver/testserver.go
--- a/client/testserver/testserver.go
+++ b/client/testserver/testserver.go
@@ -14,7 +14,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 var (
diff --git a/cmd/check/command/command.go b/cmd/check/command/command.go
--- a/cmd/check/command/command.go
+++ b/cmd/check/command/command.go
@@ -1,11 +1,11 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func New(config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/check/command/command_test.go b/cmd/check/command/command_test.go
--- a/cmd/check/command/command_test.go
+++ b/cmd/check/command/command_test.go
@@ -4,11 +4,11 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/healthcheck"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/cmd/check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/check/main.go b/cmd/check/main.go
--- a/cmd/check/main.go
+++ b/cmd/check/main.go
@@ -4,12 +4,12 @@
 	"fmt"
 	"os"
 
-	checkCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
+	checkCmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
 )
 
 func main() {
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command.go b/cmd/gitlab-shell-authorized-keys-check/command/command.go
--- a/cmd/gitlab-shell-authorized-keys-check/command/command.go
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command.go
@@ -1,12 +1,12 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/gitlab-shell-authorized-keys-check/command/command_test.go b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
--- a/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
+++ b/cmd/gitlab-shell-authorized-keys-check/command/command_test.go
@@ -4,12 +4,12 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-keys-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-shell-authorized-keys-check/main.go b/cmd/gitlab-shell-authorized-keys-check/main.go
--- a/cmd/gitlab-shell-authorized-keys-check/main.go
+++ b/cmd/gitlab-shell-authorized-keys-check/main.go
@@ -4,13 +4,13 @@
 	"fmt"
 	"os"
 
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-keys-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-keys-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
 )
 
 func main() {
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command.go b/cmd/gitlab-shell-authorized-principals-check/command/command.go
--- a/cmd/gitlab-shell-authorized-principals-check/command/command.go
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command.go
@@ -1,12 +1,12 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func New(arguments []string, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/gitlab-shell-authorized-principals-check/command/command_test.go b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
--- a/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
+++ b/cmd/gitlab-shell-authorized-principals-check/command/command_test.go
@@ -4,12 +4,12 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/authorizedprincipals"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-principals-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/authorizedprincipals"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-shell-authorized-principals-check/main.go b/cmd/gitlab-shell-authorized-principals-check/main.go
--- a/cmd/gitlab-shell-authorized-principals-check/main.go
+++ b/cmd/gitlab-shell-authorized-principals-check/main.go
@@ -4,13 +4,13 @@
 	"fmt"
 	"os"
 
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell-authorized-principals-check/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell-authorized-principals-check/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
 )
 
 func main() {
diff --git a/cmd/gitlab-shell/command/command.go b/cmd/gitlab-shell/command/command.go
--- a/cmd/gitlab-shell/command/command.go
+++ b/cmd/gitlab-shell/command/command.go
@@ -1,21 +1,21 @@
 package command
 
 import (
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/hg"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/hg"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 func New(arguments []string, env sshenv.Env, config *config.Config, readWriter *readwriter.ReadWriter) (command.Command, error) {
diff --git a/cmd/gitlab-shell/command/command_test.go b/cmd/gitlab-shell/command/command_test.go
--- a/cmd/gitlab-shell/command/command_test.go
+++ b/cmd/gitlab-shell/command/command_test.go
@@ -5,20 +5,20 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	cmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/discover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/personalaccesstoken"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/receivepack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorrecover"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/twofactorverify"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadarchive"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/uploadpack"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	cmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/receivepack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadarchive"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/uploadpack"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-shell/main.go b/cmd/gitlab-shell/main.go
--- a/cmd/gitlab-shell/main.go
+++ b/cmd/gitlab-shell/main.go
@@ -11,14 +11,14 @@
 	"gitlab.com/gitlab-org/labkit/fips"
 	"gitlab.com/gitlab-org/labkit/log"
 
-	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 var (
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -24,7 +24,7 @@
 	"github.com/stretchr/testify/require"
 	gitalyClient "gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 	"golang.org/x/crypto/ssh"
 )
 
diff --git a/cmd/gitlab-sshd/main.go b/cmd/gitlab-sshd/main.go
--- a/cmd/gitlab-sshd/main.go
+++ b/cmd/gitlab-sshd/main.go
@@ -8,10 +8,10 @@
 	"syscall"
 	"time"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/logger"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshd"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/logger"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshd"
 
 	"gitlab.com/gitlab-org/labkit/log"
 	"gitlab.com/gitlab-org/labkit/monitoring"
@@ -99,11 +99,12 @@
 		sig := <-done
 		signal.Reset(syscall.SIGINT, syscall.SIGTERM)
 
-		log.WithContextFields(ctx, log.Fields{"shutdown_timeout_s": cfg.Server.GracePeriodSeconds, "signal": sig.String()}).Info("Shutdown initiated")
+		gracePeriod := time.Duration(cfg.Server.GracePeriod)
+		log.WithContextFields(ctx, log.Fields{"shutdown_timeout_s": gracePeriod.Seconds(), "signal": sig.String()}).Info("Shutdown initiated")
 
 		server.Shutdown()
 
-		<-time.After(cfg.Server.GracePeriod())
+		<-time.After(gracePeriod)
 
 		cancel()
 
diff --git a/config.yml.example b/config.yml.example
--- a/config.yml.example
+++ b/config.yml.example
@@ -92,16 +92,30 @@
   web_listen: "localhost:9122"
   # Maximum number of concurrent sessions allowed on a single SSH connection. Defaults to 10.
   concurrent_sessions_limit: 10
-  # Sets an interval after which server will send keepalive message to a client
+  # Sets an interval after which server will send keepalive message to a client. Defaults to 15s.
   client_alive_interval: 15
-  # The server waits for this time (in seconds) for the ongoing connections to complete before shutting down. Defaults to 10.
+  # The server waits for this time for the ongoing connections to complete before shutting down. Defaults to 10s.
   grace_period: 10
+  # The server disconnects after this time if the user has not successfully logged in. Defaults to 60s.
+  login_grace_time: 60
+  # A short timeout to decide to abort the connection if the protocol header is not seen within it. Defaults to 500ms
+  proxy_header_timeout: 500ms
   # The endpoint that returns 200 OK if the server is ready to receive incoming connections; otherwise, it returns 503 Service Unavailable. Defaults to "/start".
   readiness_probe: "/start"
   # The endpoint that returns 200 OK if the server is alive. Defaults to "/health".
   liveness_probe: "/health"
+  # Specifies the available message authentication code algorithms that are used for protecting data integrity
+  macs: [hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1]
+  # Specifies the available Key Exchange algorithms
+  kex_algorithms: [curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1]
+  # Specified the ciphers allowed
+  ciphers: [aes128-gcm@openssh.com, chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-ctr, aes192-ctr,aes256-ctr]
   # SSH host key files.
   host_key_files:
     - /run/secrets/ssh-hostkeys/ssh_host_rsa_key
     - /run/secrets/ssh-hostkeys/ssh_host_ecdsa_key
     - /run/secrets/ssh-hostkeys/ssh_host_ed25519_key
+  host_key_certs:
+    - /run/secrets/ssh-hostkeys/ssh_host_rsa_key-cert.pub
+    - /run/secrets/ssh-hostkeys/ssh_host_ecdsa_key-cert.pub
+    - /run/secrets/ssh-hostkeys/ssh_host_ed25519_key-cert.pub
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module gitlab.com/gitlab-org/gitlab-shell
+module gitlab.com/gitlab-org/gitlab-shell/v14
 
 go 1.17
 
@@ -11,9 +11,10 @@
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.2
 	github.com/prometheus/client_golang v1.12.1
+	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.7.0
 	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
-	gitlab.com/gitlab-org/labkit v1.14.0
+	gitlab.com/gitlab-org/labkit v1.16.0
 	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/grpc v1.40.0
@@ -59,7 +60,6 @@
 	github.com/prometheus/procfs v0.7.3 // indirect
 	github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect
 	github.com/shirou/gopsutil/v3 v3.21.2 // indirect
-	github.com/sirupsen/logrus v1.8.1 // indirect
 	github.com/tinylib/msgp v1.1.2 // indirect
 	github.com/tklauser/go-sysconf v0.3.4 // indirect
 	github.com/tklauser/numcpus v0.2.1 // indirect
@@ -81,4 +81,4 @@
 	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
 )
 
-replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80
+replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -887,16 +887,17 @@
 gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059 h1:X7+3GQIxUpScXpIMCU5+sfpYvZyBIQ3GMlEosP7Jssw=
 gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
+gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2 h1:zz/4sD22gZqvAdBgd6gYLRIbvrgoQLDE7emPK0sXC6o=
 gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
-gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80 h1:rVzTLlEDlX/ocWqNxeuxb9XklLzmI9FdP0teRqkSg00=
-gitlab.com/gitlab-org/golang-crypto v0.0.0-20220128174055-5be136049a80/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed h1:aXSyBpG6K/QsTGevZnpFoDR7Nwvn24RpkDoWe37B8eY=
+gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
 gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
 gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
 gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
 gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
-gitlab.com/gitlab-org/labkit v1.14.0 h1:LSrvHgybidPyH8fHnsy1GBghrLR4kFObFrtZwUfCgAI=
-gitlab.com/gitlab-org/labkit v1.14.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
+gitlab.com/gitlab-org/labkit v1.16.0 h1:Vm3NAMZ8RqAunXlvPWby3GJ2R35vsYGP6Uu0YjyMIlY=
+gitlab.com/gitlab-org/labkit v1.16.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
diff --git a/internal/command/authorizedkeys/authorized_keys.go b/internal/command/authorizedkeys/authorized_keys.go
--- a/internal/command/authorizedkeys/authorized_keys.go
+++ b/internal/command/authorizedkeys/authorized_keys.go
@@ -5,11 +5,11 @@
 	"fmt"
 	"strconv"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/keyline"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/keyline"
 )
 
 type Command struct {
diff --git a/internal/command/authorizedkeys/authorized_keys_test.go b/internal/command/authorizedkeys/authorized_keys_test.go
--- a/internal/command/authorizedkeys/authorized_keys_test.go
+++ b/internal/command/authorizedkeys/authorized_keys_test.go
@@ -9,10 +9,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/command/authorizedprincipals/authorized_principals.go b/internal/command/authorizedprincipals/authorized_principals.go
--- a/internal/command/authorizedprincipals/authorized_principals.go
+++ b/internal/command/authorizedprincipals/authorized_principals.go
@@ -4,10 +4,10 @@
 	"context"
 	"fmt"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/keyline"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/keyline"
 )
 
 type Command struct {
diff --git a/internal/command/authorizedprincipals/authorized_principals_test.go b/internal/command/authorizedprincipals/authorized_principals_test.go
--- a/internal/command/authorizedprincipals/authorized_principals_test.go
+++ b/internal/command/authorizedprincipals/authorized_principals_test.go
@@ -7,9 +7,9 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func TestExecute(t *testing.T) {
diff --git a/internal/command/command.go b/internal/command/command.go
--- a/internal/command/command.go
+++ b/internal/command/command.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/tracing"
 )
diff --git a/internal/command/command_test.go b/internal/command/command_test.go
--- a/internal/command/command_test.go
+++ b/internal/command/command_test.go
@@ -6,7 +6,7 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 	"gitlab.com/gitlab-org/labkit/correlation"
 )
 
diff --git a/internal/command/commandargs/shell.go b/internal/command/commandargs/shell.go
--- a/internal/command/commandargs/shell.go
+++ b/internal/command/commandargs/shell.go
@@ -6,7 +6,7 @@
 	"strings"
 
 	"github.com/mattn/go-shellwords"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 const (
diff --git a/internal/command/discover/discover.go b/internal/command/discover/discover.go
--- a/internal/command/discover/discover.go
+++ b/internal/command/discover/discover.go
@@ -4,10 +4,10 @@
 	"context"
 	"fmt"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Command struct {
diff --git a/internal/command/discover/discover_test.go b/internal/command/discover/discover_test.go
--- a/internal/command/discover/discover_test.go
+++ b/internal/command/discover/discover_test.go
@@ -10,10 +10,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/command/healthcheck/healthcheck.go b/internal/command/healthcheck/healthcheck.go
--- a/internal/command/healthcheck/healthcheck.go
+++ b/internal/command/healthcheck/healthcheck.go
@@ -4,9 +4,9 @@
 	"context"
 	"fmt"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/healthcheck"
 )
 
 var (
diff --git a/internal/command/healthcheck/healthcheck_test.go b/internal/command/healthcheck/healthcheck_test.go
--- a/internal/command/healthcheck/healthcheck_test.go
+++ b/internal/command/healthcheck/healthcheck_test.go
@@ -9,10 +9,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/healthcheck"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/healthcheck"
 )
 
 var (
diff --git a/internal/command/hg/hg.go b/internal/command/hg/hg.go
--- a/internal/command/hg/hg.go
+++ b/internal/command/hg/hg.go
@@ -10,11 +10,11 @@
 	"strings"
 	"syscall"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/hgaccesslevel"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/hgaccesslevel"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func Exec(filename string, args []string, env []string) error {
diff --git a/internal/command/hg/hg_test.go b/internal/command/hg/hg_test.go
--- a/internal/command/hg/hg_test.go
+++ b/internal/command/hg/hg_test.go
@@ -8,11 +8,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 type fakeExec struct {
diff --git a/internal/command/lfsauthenticate/lfsauthenticate.go b/internal/command/lfsauthenticate/lfsauthenticate.go
--- a/internal/command/lfsauthenticate/lfsauthenticate.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate.go
@@ -8,12 +8,12 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/lfsauthenticate"
 )
 
 const (
diff --git a/internal/command/lfsauthenticate/lfsauthenticate_test.go b/internal/command/lfsauthenticate/lfsauthenticate_test.go
--- a/internal/command/lfsauthenticate/lfsauthenticate_test.go
+++ b/internal/command/lfsauthenticate/lfsauthenticate_test.go
@@ -10,13 +10,13 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/lfsauthenticate"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/lfsauthenticate"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestFailedRequests(t *testing.T) {
diff --git a/internal/command/personalaccesstoken/personalaccesstoken.go b/internal/command/personalaccesstoken/personalaccesstoken.go
--- a/internal/command/personalaccesstoken/personalaccesstoken.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken.go
@@ -10,10 +10,10 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/personalaccesstoken"
 )
 
 const (
diff --git a/internal/command/personalaccesstoken/personalaccesstoken_test.go b/internal/command/personalaccesstoken/personalaccesstoken_test.go
--- a/internal/command/personalaccesstoken/personalaccesstoken_test.go
+++ b/internal/command/personalaccesstoken/personalaccesstoken_test.go
@@ -10,11 +10,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/personalaccesstoken"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/personalaccesstoken"
 )
 
 var (
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -7,9 +7,9 @@
 
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/handler"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
diff --git a/internal/command/receivepack/gitalycall_test.go b/internal/command/receivepack/gitalycall_test.go
--- a/internal/command/receivepack/gitalycall_test.go
+++ b/internal/command/receivepack/gitalycall_test.go
@@ -8,12 +8,12 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestReceivePack(t *testing.T) {
diff --git a/internal/command/receivepack/receivepack.go b/internal/command/receivepack/receivepack.go
--- a/internal/command/receivepack/receivepack.go
+++ b/internal/command/receivepack/receivepack.go
@@ -3,12 +3,12 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/customaction"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/customaction"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 type Command struct {
diff --git a/internal/command/receivepack/receivepack_test.go b/internal/command/receivepack/receivepack_test.go
--- a/internal/command/receivepack/receivepack_test.go
+++ b/internal/command/receivepack/receivepack_test.go
@@ -7,11 +7,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestForbiddenAccess(t *testing.T) {
diff --git a/internal/command/shared/accessverifier/accessverifier.go b/internal/command/shared/accessverifier/accessverifier.go
--- a/internal/command/shared/accessverifier/accessverifier.go
+++ b/internal/command/shared/accessverifier/accessverifier.go
@@ -4,11 +4,11 @@
 	"context"
 	"errors"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/console"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 )
 
 type Response = accessverifier.Response
diff --git a/internal/command/shared/accessverifier/accessverifier_test.go b/internal/command/shared/accessverifier/accessverifier_test.go
--- a/internal/command/shared/accessverifier/accessverifier_test.go
+++ b/internal/command/shared/accessverifier/accessverifier_test.go
@@ -10,11 +10,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 )
 
 var (
diff --git a/internal/command/shared/customaction/customaction.go b/internal/command/shared/customaction/customaction.go
--- a/internal/command/shared/customaction/customaction.go
+++ b/internal/command/shared/customaction/customaction.go
@@ -9,12 +9,12 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/pktline"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/pktline"
 )
 
 type Request struct {
diff --git a/internal/command/shared/customaction/customaction_test.go b/internal/command/shared/customaction/customaction_test.go
--- a/internal/command/shared/customaction/customaction_test.go
+++ b/internal/command/shared/customaction/customaction_test.go
@@ -10,10 +10,10 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 )
 
 func TestExecuteEOFSent(t *testing.T) {
diff --git a/internal/command/shared/hgaccesslevel/hgaccesslevel.go b/internal/command/shared/hgaccesslevel/hgaccesslevel.go
--- a/internal/command/shared/hgaccesslevel/hgaccesslevel.go
+++ b/internal/command/shared/hgaccesslevel/hgaccesslevel.go
@@ -5,10 +5,10 @@
 	"errors"
 	"fmt"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 )
 
 type Response = accessverifier.HgResponse
diff --git a/internal/command/twofactorrecover/twofactorrecover.go b/internal/command/twofactorrecover/twofactorrecover.go
--- a/internal/command/twofactorrecover/twofactorrecover.go
+++ b/internal/command/twofactorrecover/twofactorrecover.go
@@ -8,10 +8,10 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorrecover"
 )
 
 const readerLimit = 1024
diff --git a/internal/command/twofactorrecover/twofactorrecover_test.go b/internal/command/twofactorrecover/twofactorrecover_test.go
--- a/internal/command/twofactorrecover/twofactorrecover_test.go
+++ b/internal/command/twofactorrecover/twofactorrecover_test.go
@@ -11,11 +11,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorrecover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorrecover"
 )
 
 var (
diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -7,10 +7,10 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
 )
 
 type Command struct {
diff --git a/internal/command/twofactorverify/twofactorverify_test.go b/internal/command/twofactorverify/twofactorverify_test.go
--- a/internal/command/twofactorverify/twofactorverify_test.go
+++ b/internal/command/twofactorverify/twofactorverify_test.go
@@ -10,11 +10,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/twofactorverify"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
 )
 
 func setup(t *testing.T) []testserver.TestRequestHandler {
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -7,9 +7,9 @@
 
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/handler"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
diff --git a/internal/command/uploadarchive/gitalycall_test.go b/internal/command/uploadarchive/gitalycall_test.go
--- a/internal/command/uploadarchive/gitalycall_test.go
+++ b/internal/command/uploadarchive/gitalycall_test.go
@@ -8,12 +8,12 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestUploadArchive(t *testing.T) {
diff --git a/internal/command/uploadarchive/uploadarchive.go b/internal/command/uploadarchive/uploadarchive.go
--- a/internal/command/uploadarchive/uploadarchive.go
+++ b/internal/command/uploadarchive/uploadarchive.go
@@ -3,11 +3,11 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 type Command struct {
diff --git a/internal/command/uploadarchive/uploadarchive_test.go b/internal/command/uploadarchive/uploadarchive_test.go
--- a/internal/command/uploadarchive/uploadarchive_test.go
+++ b/internal/command/uploadarchive/uploadarchive_test.go
@@ -7,11 +7,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestForbiddenAccess(t *testing.T) {
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -7,9 +7,9 @@
 
 	"gitlab.com/gitlab-org/gitaly/v14/client"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/handler"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
 )
 
 func (c *Command) performGitalyCall(ctx context.Context, response *accessverifier.Response) error {
diff --git a/internal/command/uploadpack/gitalycall_test.go b/internal/command/uploadpack/gitalycall_test.go
--- a/internal/command/uploadpack/gitalycall_test.go
+++ b/internal/command/uploadpack/gitalycall_test.go
@@ -8,12 +8,12 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/correlation"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestUploadPack(t *testing.T) {
diff --git a/internal/command/uploadpack/uploadpack.go b/internal/command/uploadpack/uploadpack.go
--- a/internal/command/uploadpack/uploadpack.go
+++ b/internal/command/uploadpack/uploadpack.go
@@ -3,12 +3,12 @@
 import (
 	"context"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/customaction"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/customaction"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 type Command struct {
diff --git a/internal/command/uploadpack/uploadpack_test.go b/internal/command/uploadpack/uploadpack_test.go
--- a/internal/command/uploadpack/uploadpack_test.go
+++ b/internal/command/uploadpack/uploadpack_test.go
@@ -7,11 +7,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper/requesthandlers"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper/requesthandlers"
 )
 
 func TestForbiddenAccess(t *testing.T) {
diff --git a/internal/config/config.go b/internal/config/config.go
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -12,9 +12,9 @@
 
 	yaml "gopkg.in/yaml.v2"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitaly"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 const (
@@ -27,17 +27,25 @@
 	defaultHgrcPath  string = "/opt/gitlab/etc/docker.hgrc:/etc/gitlab/heptapod.hgrc"
 )
 
+type YamlDuration time.Duration
+
 type ServerConfig struct {
-	Listen                     string   `yaml:"listen,omitempty"`
-	ProxyProtocol              bool     `yaml:"proxy_protocol,omitempty"`
-	ProxyPolicy                string   `yaml:"proxy_policy,omitempty"`
-	WebListen                  string   `yaml:"web_listen,omitempty"`
-	ConcurrentSessionsLimit    int64    `yaml:"concurrent_sessions_limit,omitempty"`
-	ClientAliveIntervalSeconds int64    `yaml:"client_alive_interval,omitempty"`
-	GracePeriodSeconds         uint64   `yaml:"grace_period"`
-	ReadinessProbe             string   `yaml:"readiness_probe"`
-	LivenessProbe              string   `yaml:"liveness_probe"`
-	HostKeyFiles               []string `yaml:"host_key_files,omitempty"`
+	Listen                  string       `yaml:"listen,omitempty"`
+	ProxyProtocol           bool         `yaml:"proxy_protocol,omitempty"`
+	ProxyPolicy             string       `yaml:"proxy_policy,omitempty"`
+	WebListen               string       `yaml:"web_listen,omitempty"`
+	ConcurrentSessionsLimit int64        `yaml:"concurrent_sessions_limit,omitempty"`
+	ClientAliveInterval     YamlDuration `yaml:"client_alive_interval,omitempty"`
+	GracePeriod             YamlDuration `yaml:"grace_period"`
+	ProxyHeaderTimeout      YamlDuration `yaml:"proxy_header_timeout"`
+	LoginGraceTime          YamlDuration `yaml:"login_grace_time"`
+	ReadinessProbe          string       `yaml:"readiness_probe"`
+	LivenessProbe           string       `yaml:"liveness_probe"`
+	HostKeyFiles            []string     `yaml:"host_key_files,omitempty"`
+	HostCertFiles           []string     `yaml:"host_cert_files,omitempty"`
+	MACs                    []string     `yaml:"macs"`
+	KexAlgorithms           []string     `yaml:"kex_algorithms"`
+	Ciphers                 []string     `yaml:"ciphers"`
 }
 
 type HttpSettingsConfig struct {
@@ -103,13 +111,15 @@
 	}
 
 	DefaultServerConfig = ServerConfig{
-		Listen:                     "[::]:22",
-		WebListen:                  "localhost:9122",
-		ConcurrentSessionsLimit:    10,
-		GracePeriodSeconds:         10,
-		ClientAliveIntervalSeconds: 15,
-		ReadinessProbe:             "/start",
-		LivenessProbe:              "/health",
+		Listen:                  "[::]:22",
+		WebListen:               "localhost:9122",
+		ConcurrentSessionsLimit: 10,
+		GracePeriod:             YamlDuration(10 * time.Second),
+		ClientAliveInterval:     YamlDuration(15 * time.Second),
+		ProxyHeaderTimeout:      YamlDuration(500 * time.Millisecond),
+		LoginGraceTime:          YamlDuration(60 * time.Second),
+		ReadinessProbe:          "/start",
+		LivenessProbe:           "/health",
 		HostKeyFiles: []string{
 			"/run/secrets/ssh-hostkeys/ssh_host_rsa_key",
 			"/run/secrets/ssh-hostkeys/ssh_host_ecdsa_key",
@@ -118,12 +128,15 @@
 	}
 )
 
-func (sc *ServerConfig) ClientAliveInterval() time.Duration {
-	return time.Duration(sc.ClientAliveIntervalSeconds) * time.Second
-}
+func (d *YamlDuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	var intDuration int
+	if err := unmarshal(&intDuration); err != nil {
+		return unmarshal((*time.Duration)(d))
+	}
 
-func (sc *ServerConfig) GracePeriod() time.Duration {
-	return time.Duration(sc.GracePeriodSeconds) * time.Second
+	*d = YamlDuration(time.Duration(intDuration) * time.Second)
+
+	return nil
 }
 
 func (c *Config) ApplyGlobalState() {
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -3,12 +3,14 @@
 import (
 	"os"
 	"testing"
+	"time"
 
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/stretchr/testify/require"
+	yaml "gopkg.in/yaml.v2"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 func TestConfigApplyGlobalState(t *testing.T) {
@@ -39,7 +41,7 @@
 	require.NoError(t, err)
 
 	var actualNames []string
-	for _, m := range ms[0:10] {
+	for _, m := range ms[0:9] {
 		actualNames = append(actualNames, m.GetName())
 	}
 
@@ -47,7 +49,6 @@
 		"gitlab_shell_http_in_flight_requests",
 		"gitlab_shell_http_request_duration_seconds",
 		"gitlab_shell_http_requests_total",
-		"gitlab_shell_sshd_canceled_sessions",
 		"gitlab_shell_sshd_concurrent_limited_sessions_total",
 		"gitlab_shell_sshd_in_flight_connections",
 		"gitlab_shell_sshd_session_duration_seconds",
@@ -58,3 +59,40 @@
 
 	require.Equal(t, expectedMetricNames, actualNames)
 }
+
+func TestNewFromDir(t *testing.T) {
+	testhelper.PrepareTestRootDir(t)
+
+	cfg, err := NewFromDir(testhelper.TestRoot)
+	require.NoError(t, err)
+
+	require.Equal(t, 10*time.Second, time.Duration(cfg.Server.GracePeriod))
+	require.Equal(t, 1*time.Minute, time.Duration(cfg.Server.ClientAliveInterval))
+	require.Equal(t, 500*time.Millisecond, time.Duration(cfg.Server.ProxyHeaderTimeout))
+}
+
+func TestYAMLDuration(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		data     string
+		duration time.Duration
+	}{
+		{"seconds assumed by default", "duration: 10", 10 * time.Second},
+		{"milliseconds are parsed", "duration: 500ms", 500 * time.Millisecond},
+		{"minutes are parsed", "duration: 1m", 1 * time.Minute},
+	}
+
+	type durationCfg struct {
+		Duration YamlDuration `yaml:"duration"`
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			var cfg durationCfg
+			err := yaml.Unmarshal([]byte(tc.data), &cfg)
+			require.NoError(t, err)
+
+			require.Equal(t, tc.duration, time.Duration(cfg.Duration))
+		})
+	}
+}
diff --git a/internal/executable/executable_test.go b/internal/executable/executable_test.go
--- a/internal/executable/executable_test.go
+++ b/internal/executable/executable_test.go
@@ -4,7 +4,7 @@
 	"errors"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 
 	"github.com/stretchr/testify/require"
 )
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
--- a/internal/gitaly/gitaly.go
+++ b/internal/gitaly/gitaly.go
@@ -17,7 +17,7 @@
 	"gitlab.com/gitlab-org/labkit/log"
 	grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 type Command struct {
diff --git a/internal/gitaly/gitaly_test.go b/internal/gitaly/gitaly_test.go
--- a/internal/gitaly/gitaly_test.go
+++ b/internal/gitaly/gitaly_test.go
@@ -7,7 +7,7 @@
 	"github.com/prometheus/client_golang/prometheus/testutil"
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 func TestPrometheusMetrics(t *testing.T) {
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -3,14 +3,13 @@
 import (
 	"context"
 	"fmt"
-	"net"
 	"net/http"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 const (
@@ -86,7 +85,7 @@
 		request.KeyId = args.GitlabKeyId
 	}
 
-	request.CheckIp = parseIP(args.Env.RemoteAddr)
+	request.CheckIp = gitlabnet.ParseIP(args.Env.RemoteAddr)
 
 	response, err := c.client.Post(ctx, "/allowed", request)
 	if err != nil {
@@ -117,18 +116,3 @@
 func (r *Response) IsCustomAction() bool {
 	return r.StatusCode == http.StatusMultipleChoices
 }
-
-func parseIP(remoteAddr string) string {
-	// The remoteAddr field can be filled by:
-	// 1. An IP address via the SSH_CONNECTION environment variable
-	// 2. A host:port combination via the PROXY protocol
-	ip, _, err := net.SplitHostPort(remoteAddr)
-
-	// If we don't have a port or can't parse this address for some reason,
-	// just return the original string.
-	if err != nil {
-		return remoteAddr
-	}
-
-	return ip
-}
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -11,11 +11,11 @@
 
 	"github.com/stretchr/testify/require"
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 var (
diff --git a/internal/gitlabnet/accessverifier/hg_client.go b/internal/gitlabnet/accessverifier/hg_client.go
--- a/internal/gitlabnet/accessverifier/hg_client.go
+++ b/internal/gitlabnet/accessverifier/hg_client.go
@@ -5,10 +5,10 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 type HgClient struct {
diff --git a/internal/gitlabnet/accessverifier/hg_client_test.go b/internal/gitlabnet/accessverifier/hg_client_test.go
--- a/internal/gitlabnet/accessverifier/hg_client_test.go
+++ b/internal/gitlabnet/accessverifier/hg_client_test.go
@@ -10,11 +10,11 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 func buildHgExpectedResponse() *HgResponse {
diff --git a/internal/gitlabnet/authorizedkeys/client.go b/internal/gitlabnet/authorizedkeys/client.go
--- a/internal/gitlabnet/authorizedkeys/client.go
+++ b/internal/gitlabnet/authorizedkeys/client.go
@@ -5,9 +5,9 @@
 	"fmt"
 	"net/url"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 const (
diff --git a/internal/gitlabnet/authorizedkeys/client_test.go b/internal/gitlabnet/authorizedkeys/client_test.go
--- a/internal/gitlabnet/authorizedkeys/client_test.go
+++ b/internal/gitlabnet/authorizedkeys/client_test.go
@@ -7,9 +7,9 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/gitlabnet/client.go b/internal/gitlabnet/client.go
--- a/internal/gitlabnet/client.go
+++ b/internal/gitlabnet/client.go
@@ -3,11 +3,12 @@
 import (
 	"encoding/json"
 	"fmt"
+	"net"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
@@ -34,3 +35,18 @@
 
 	return nil
 }
+
+func ParseIP(remoteAddr string) string {
+	// The remoteAddr field can be filled by:
+	// 1. An IP address via the SSH_CONNECTION environment variable
+	// 2. A host:port combination via the PROXY protocol
+	ip, _, err := net.SplitHostPort(remoteAddr)
+
+	// If we don't have a port or can't parse this address for some reason,
+	// just return the original string.
+	if err != nil {
+		return remoteAddr
+	}
+
+	return ip
+}
diff --git a/internal/gitlabnet/discover/client.go b/internal/gitlabnet/discover/client.go
--- a/internal/gitlabnet/discover/client.go
+++ b/internal/gitlabnet/discover/client.go
@@ -6,10 +6,10 @@
 	"net/http"
 	"net/url"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/discover/client_test.go b/internal/gitlabnet/discover/client_test.go
--- a/internal/gitlabnet/discover/client_test.go
+++ b/internal/gitlabnet/discover/client_test.go
@@ -8,11 +8,11 @@
 	"net/url"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 var (
diff --git a/internal/gitlabnet/healthcheck/client.go b/internal/gitlabnet/healthcheck/client.go
--- a/internal/gitlabnet/healthcheck/client.go
+++ b/internal/gitlabnet/healthcheck/client.go
@@ -5,9 +5,9 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 const (
diff --git a/internal/gitlabnet/healthcheck/client_test.go b/internal/gitlabnet/healthcheck/client_test.go
--- a/internal/gitlabnet/healthcheck/client_test.go
+++ b/internal/gitlabnet/healthcheck/client_test.go
@@ -6,8 +6,8 @@
 	"net/http"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 
 	"github.com/stretchr/testify/require"
 )
diff --git a/internal/gitlabnet/lfsauthenticate/client.go b/internal/gitlabnet/lfsauthenticate/client.go
--- a/internal/gitlabnet/lfsauthenticate/client.go
+++ b/internal/gitlabnet/lfsauthenticate/client.go
@@ -6,10 +6,10 @@
 	"net/http"
 	"strings"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/lfsauthenticate/client_test.go b/internal/gitlabnet/lfsauthenticate/client_test.go
--- a/internal/gitlabnet/lfsauthenticate/client_test.go
+++ b/internal/gitlabnet/lfsauthenticate/client_test.go
@@ -9,9 +9,9 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 const (
diff --git a/internal/gitlabnet/personalaccesstoken/client.go b/internal/gitlabnet/personalaccesstoken/client.go
--- a/internal/gitlabnet/personalaccesstoken/client.go
+++ b/internal/gitlabnet/personalaccesstoken/client.go
@@ -6,11 +6,11 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/personalaccesstoken/client_test.go b/internal/gitlabnet/personalaccesstoken/client_test.go
--- a/internal/gitlabnet/personalaccesstoken/client_test.go
+++ b/internal/gitlabnet/personalaccesstoken/client_test.go
@@ -8,11 +8,11 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 var (
diff --git a/internal/gitlabnet/twofactorrecover/client.go b/internal/gitlabnet/twofactorrecover/client.go
--- a/internal/gitlabnet/twofactorrecover/client.go
+++ b/internal/gitlabnet/twofactorrecover/client.go
@@ -6,11 +6,11 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/twofactorrecover/client_test.go b/internal/gitlabnet/twofactorrecover/client_test.go
--- a/internal/gitlabnet/twofactorrecover/client_test.go
+++ b/internal/gitlabnet/twofactorrecover/client_test.go
@@ -8,11 +8,11 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 var (
diff --git a/internal/gitlabnet/twofactorverify/client.go b/internal/gitlabnet/twofactorverify/client.go
--- a/internal/gitlabnet/twofactorverify/client.go
+++ b/internal/gitlabnet/twofactorverify/client.go
@@ -6,11 +6,11 @@
 	"fmt"
 	"net/http"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 )
 
 type Client struct {
diff --git a/internal/gitlabnet/twofactorverify/client_test.go b/internal/gitlabnet/twofactorverify/client_test.go
--- a/internal/gitlabnet/twofactorverify/client_test.go
+++ b/internal/gitlabnet/twofactorverify/client_test.go
@@ -7,13 +7,13 @@
 	"net/http"
 	"testing"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/discover"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/discover"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/client"
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func initialize(t *testing.T) []testserver.TestRequestHandler {
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -11,10 +11,10 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitaly"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitaly"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/labkit/log"
@@ -58,13 +58,11 @@
 	exitStatus, err := handler(childCtx, conn)
 
 	if err != nil {
-		if grpcstatus.Convert(err).Code() == grpccodes.Unavailable {
-			ctxlog.WithError(fmt.Errorf("RunGitalyCommand: %v", err)).Error("Gitaly is unavailable")
+		ctxlog.WithError(err).WithFields(log.Fields{"exit_status": exitStatus}).Error("Failed to execute Git command")
 
-			return fmt.Errorf("The git server, Gitaly, is not available at this time. Please contact your administrator.")
+		if grpcstatus.Code(err) == grpccodes.Unavailable {
+			return grpcstatus.Error(grpccodes.Unavailable, "The git server, Gitaly, is not available at this time. Please contact your administrator.")
 		}
-
-		ctxlog.WithError(err).WithFields(log.Fields{"exit_status": exitStatus}).Error("Failed to execute Git command")
 	}
 
 	return err
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -12,10 +12,10 @@
 	grpcstatus "google.golang.org/grpc/status"
 
 	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/commandargs"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/accessverifier"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn) (int32, error) {
@@ -84,10 +84,8 @@
 		},
 	)
 
-	expectedErr := grpcstatus.Error(grpccodes.Unavailable, "error")
-	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, expectedErr))
-
-	require.EqualError(t, err, "The git server, Gitaly, is not available at this time. Please contact your administrator.")
+	err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, grpcstatus.Error(grpccodes.Unavailable, "error")))
+	require.Equal(t, err, grpcstatus.Error(grpccodes.Unavailable, "The git server, Gitaly, is not available at this time. Please contact your administrator."))
 }
 
 func TestRunGitalyCommandMetadata(t *testing.T) {
diff --git a/internal/keyline/key_line.go b/internal/keyline/key_line.go
--- a/internal/keyline/key_line.go
+++ b/internal/keyline/key_line.go
@@ -7,8 +7,8 @@
 	"regexp"
 	"strings"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/executable"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/executable"
 )
 
 var (
diff --git a/internal/keyline/key_line_test.go b/internal/keyline/key_line_test.go
--- a/internal/keyline/key_line_test.go
+++ b/internal/keyline/key_line_test.go
@@ -4,7 +4,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func TestFailingNewPublicKeyLine(t *testing.T) {
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -9,7 +9,7 @@
 
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func logFmt(inFmt string) string {
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
--- a/internal/logger/logger_test.go
+++ b/internal/logger/logger_test.go
@@ -8,7 +8,7 @@
 	"github.com/stretchr/testify/require"
 	"gitlab.com/gitlab-org/labkit/log"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 )
 
 func TestConfigure(t *testing.T) {
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -77,15 +77,6 @@
 		},
 	)
 
-	SshdCanceledSessions = promauto.NewCounter(
-		prometheus.CounterOpts{
-			Namespace: namespace,
-			Subsystem: sshdSubsystem,
-			Name:      sshdCanceledSessionsName,
-			Help:      "The number of canceled gitlab-sshd sessions.",
-		},
-	)
-
 	SliSshdSessionsTotal = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Name: sliSshdSessionsTotalName,
diff --git a/internal/sshd/connection.go b/internal/sshd/connection.go
--- a/internal/sshd/connection.go
+++ b/internal/sshd/connection.go
@@ -2,77 +2,124 @@
 
 import (
 	"context"
+	"errors"
+	"net"
+	"strings"
 	"time"
 
+	"github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/semaphore"
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
-const KeepAliveMsg = "keepalive@openssh.com"
+const (
+	KeepAliveMsg   = "keepalive@openssh.com"
+	NotOurRefError = `exit status 128, stderr: "fatal: git upload-pack: not our ref `
+)
 
 var EOFTimeout = 10 * time.Second
 
 type connection struct {
 	cfg                *config.Config
 	concurrentSessions *semaphore.Weighted
+	nconn              net.Conn
+	maxSessions        int64
 	remoteAddr         string
-	sconn              *ssh.ServerConn
-	maxSessions        int64
 }
 
-type channelHandler func(context.Context, ssh.Channel, <-chan *ssh.Request) error
+type channelHandler func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error
 
-func newConnection(cfg *config.Config, remoteAddr string, sconn *ssh.ServerConn) *connection {
+func newConnection(cfg *config.Config, nconn net.Conn) *connection {
 	maxSessions := cfg.Server.ConcurrentSessionsLimit
 
 	return &connection{
 		cfg:                cfg,
 		maxSessions:        maxSessions,
 		concurrentSessions: semaphore.NewWeighted(maxSessions),
-		remoteAddr:         remoteAddr,
-		sconn:              sconn,
+		nconn:              nconn,
+		remoteAddr:         nconn.RemoteAddr().String(),
 	}
 }
 
-func (c *connection) handle(ctx context.Context, chans <-chan ssh.NewChannel, handler channelHandler) {
-	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+func (c *connection) handle(ctx context.Context, srvCfg *ssh.ServerConfig, handler channelHandler) {
+	sconn, chans, err := c.initServerConn(ctx, srvCfg)
+	if err != nil {
+		return
+	}
+
+	if c.cfg.Server.ClientAliveInterval > 0 {
+		ticker := time.NewTicker(time.Duration(c.cfg.Server.ClientAliveInterval))
+		defer ticker.Stop()
+		go c.sendKeepAliveMsg(ctx, sconn, ticker)
+	}
+
+	c.handleRequests(ctx, sconn, chans, handler)
+
+	reason := sconn.Wait()
+	log.WithContextFields(ctx, log.Fields{"reason": reason}).Info("server: handleConn: done")
+}
 
-	if c.cfg.Server.ClientAliveIntervalSeconds > 0 {
-		ticker := time.NewTicker(c.cfg.Server.ClientAliveInterval())
-		defer ticker.Stop()
-		go c.sendKeepAliveMsg(ctx, ticker)
+func (c *connection) initServerConn(ctx context.Context, srvCfg *ssh.ServerConfig) (*ssh.ServerConn, <-chan ssh.NewChannel, error) {
+	if c.cfg.Server.LoginGraceTime > 0 {
+		c.nconn.SetDeadline(time.Now().Add(time.Duration(c.cfg.Server.LoginGraceTime)))
+		defer c.nconn.SetDeadline(time.Time{})
 	}
 
+	sconn, chans, reqs, err := ssh.NewServerConn(c.nconn, srvCfg)
+	if err != nil {
+		msg := "connection: initServerConn: failed to initialize SSH connection"
+		logger := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr}).WithError(err)
+
+		if strings.Contains(err.Error(), "no common algorithm for host key") || err.Error() == "EOF" {
+			logger.Debug(msg)
+		} else {
+			logger.Warn(msg)
+		}
+
+		return nil, nil, err
+	}
+	go ssh.DiscardRequests(reqs)
+
+	return sconn, chans, err
+}
+
+func (c *connection) handleRequests(ctx context.Context, sconn *ssh.ServerConn, chans <-chan ssh.NewChannel, handler channelHandler) {
+	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
+
 	for newChannel := range chans {
 		ctxlog.WithField("channel_type", newChannel.ChannelType()).Info("connection: handle: new channel requested")
 		if newChannel.ChannelType() != "session" {
-			ctxlog.Info("connection: handle: unknown channel type")
+			ctxlog.Info("connection: handleRequests: unknown channel type")
 			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
 			continue
 		}
 		if !c.concurrentSessions.TryAcquire(1) {
-			ctxlog.Info("connection: handle: too many concurrent sessions")
+			ctxlog.Info("connection: handleRequests: too many concurrent sessions")
 			newChannel.Reject(ssh.ResourceShortage, "too many concurrent sessions")
 			metrics.SshdHitMaxSessions.Inc()
 			continue
 		}
 		channel, requests, err := newChannel.Accept()
 		if err != nil {
-			ctxlog.WithError(err).Error("connection: handle: accepting channel failed")
+			ctxlog.WithError(err).Error("connection: handleRequests: accepting channel failed")
 			c.concurrentSessions.Release(1)
 			continue
 		}
 
 		go func() {
 			defer func(started time.Time) {
-				metrics.SshdSessionDuration.Observe(time.Since(started).Seconds())
+				duration := time.Since(started).Seconds()
+				metrics.SshdSessionDuration.Observe(duration)
+				ctxlog.WithFields(log.Fields{"duration_s": duration}).Info("connection: handleRequests: done")
 			}(time.Now())
 
 			defer c.concurrentSessions.Release(1)
@@ -85,16 +132,10 @@
 			}()
 
 			metrics.SliSshdSessionsTotal.Inc()
-			err := handler(ctx, channel, requests)
+			err := handler(sconn, channel, requests)
 			if err != nil {
-				if grpcstatus.Convert(err).Code() == grpccodes.Canceled {
-					metrics.SshdCanceledSessions.Inc()
-				} else {
-					metrics.SliSshdSessionsErrorsTotal.Inc()
-				}
+				c.trackError(ctxlog, err)
 			}
-
-			ctxlog.Info("connection: handle: done")
 		}()
 	}
 
@@ -107,7 +148,7 @@
 	c.concurrentSessions.Acquire(ctx, c.maxSessions)
 }
 
-func (c *connection) sendKeepAliveMsg(ctx context.Context, ticker *time.Ticker) {
+func (c *connection) sendKeepAliveMsg(ctx context.Context, sconn *ssh.ServerConn, ticker *time.Ticker) {
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": c.remoteAddr})
 
 	for {
@@ -115,9 +156,30 @@
 		case <-ctx.Done():
 			return
 		case <-ticker.C:
-			ctxlog.Debug("session: handleShell: send keepalive message to a client")
+			ctxlog.Debug("connection: sendKeepAliveMsg: send keepalive message to a client")
 
-			c.sconn.SendRequest(KeepAliveMsg, true, nil)
+			sconn.SendRequest(KeepAliveMsg, true, nil)
 		}
 	}
 }
+
+func (c *connection) trackError(ctxlog *logrus.Entry, err error) {
+	var apiError *client.ApiError
+	if errors.As(err, &apiError) {
+		return
+	}
+
+	if errors.Is(err, disallowedcommand.Error) {
+		return
+	}
+
+	grpcCode := grpcstatus.Code(err)
+	if grpcCode == grpccodes.Canceled || grpcCode == grpccodes.Unavailable {
+		return
+	} else if grpcCode == grpccodes.Internal && strings.Contains(err.Error(), NotOurRefError) {
+		return
+	}
+
+	metrics.SliSshdSessionsErrorsTotal.Inc()
+	ctxlog.WithError(err).Warn("connection: session error")
+}
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -10,11 +10,14 @@
 	"github.com/prometheus/client_golang/prometheus/testutil"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
+	"golang.org/x/sync/semaphore"
 	grpccodes "google.golang.org/grpc/codes"
 	grpcstatus "google.golang.org/grpc/status"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 )
 
 type rejectCall struct {
@@ -79,8 +82,8 @@
 }
 
 func setup(sessionsNum int64, newChannel *fakeNewChannel) (*connection, chan ssh.NewChannel) {
-	cfg := &config.Config{Server: config.ServerConfig{ConcurrentSessionsLimit: sessionsNum, ClientAliveIntervalSeconds: 1}}
-	conn := newConnection(cfg, "127.0.0.1:50000", &ssh.ServerConn{&fakeConn{}, nil})
+	cfg := &config.Config{Server: config.ServerConfig{ConcurrentSessionsLimit: sessionsNum}}
+	conn := &connection{cfg: cfg, concurrentSessions: semaphore.NewWeighted(sessionsNum)}
 
 	chans := make(chan ssh.NewChannel, 1)
 	chans <- newChannel
@@ -94,7 +97,7 @@
 
 	numSessions := 0
 	require.NotPanics(t, func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 			numSessions += 1
 			close(chans)
 			panic("This is a panic")
@@ -112,7 +115,7 @@
 	conn, chans := setup(1, newChannel)
 
 	go func() {
-		conn.handle(context.Background(), chans, nil)
+		conn.handleRequests(context.Background(), nil, chans, nil)
 	}()
 
 	rejectionData := <-rejectCh
@@ -132,7 +135,7 @@
 	defer cancel()
 
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 			<-ctx.Done() // Keep the accepted channel open until the end of the test
 			return nil
 		})
@@ -147,7 +150,7 @@
 	conn, chans := setup(1, newChannel)
 
 	channelHandled := false
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		channelHandled = true
 		close(chans)
 		return nil
@@ -166,7 +169,7 @@
 
 	channelHandled := false
 	go func() {
-		conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+		conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 			channelHandled = true
 			return nil
 		})
@@ -184,12 +187,11 @@
 func TestClientAliveInterval(t *testing.T) {
 	f := &fakeConn{}
 
-	conn := newConnection(&config.Config{}, "127.0.0.1:50000", &ssh.ServerConn{f, nil})
-
 	ticker := time.NewTicker(time.Millisecond)
 	defer ticker.Stop()
 
-	go conn.sendKeepAliveMsg(context.Background(), ticker)
+	conn := &connection{}
+	go conn.sendKeepAliveMsg(context.Background(), &ssh.ServerConn{f, nil}, ticker)
 
 	require.Eventually(t, func() bool { return KeepAliveMsg == f.SentRequestName() }, time.Second, time.Millisecond)
 }
@@ -199,27 +201,38 @@
 	// https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#pkg-index
 	initialSessionsTotal := testutil.ToFloat64(metrics.SliSshdSessionsTotal)
 	initialSessionsErrorTotal := testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal)
-	initialCanceledSessions := testutil.ToFloat64(metrics.SshdCanceledSessions)
 
 	newChannel := &fakeNewChannel{channelType: "session"}
 
 	conn, chans := setup(1, newChannel)
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
+	conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 		close(chans)
 		return errors.New("custom error")
 	})
 
 	require.InDelta(t, initialSessionsTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
 	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
-	require.InDelta(t, initialCanceledSessions, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
 
-	conn, chans = setup(1, newChannel)
-	conn.handle(context.Background(), chans, func(context.Context, ssh.Channel, <-chan *ssh.Request) error {
-		close(chans)
-		return grpcstatus.Error(grpccodes.Canceled, "error")
-	})
+	for i, ignoredError := range []struct {
+		desc string
+		err  error
+	}{
+		{"canceled requests", grpcstatus.Error(grpccodes.Canceled, "canceled")},
+		{"unavailable Gitaly", grpcstatus.Error(grpccodes.Unavailable, "unavailable")},
+		{"api error", &client.ApiError{"api error"}},
+		{"disallowed command", disallowedcommand.Error},
+		{"not our ref", grpcstatus.Error(grpccodes.Internal, `rpc error: code = Internal desc = cmd wait: exit status 128, stderr: "fatal: git upload-pack: not our ref 9106d18f6a1b8022f6517f479696f3e3ea5e68c1"`)},
+	} {
+		ignoredError := ignoredError // Capture range variable, see "Race on loop counter" in https://go.dev/doc/articles/race_detector
+		t.Run(ignoredError.desc, func(t *testing.T) {
+			conn, chans = setup(1, newChannel)
+			conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
+				close(chans)
+				return ignoredError.err
+			})
 
-	require.InDelta(t, initialSessionsTotal+2, testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
-	require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
-	require.InDelta(t, initialCanceledSessions+1, testutil.ToFloat64(metrics.SshdCanceledSessions), 0.1)
+			require.InDelta(t, initialSessionsTotal+2+float64(i), testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
+			require.InDelta(t, initialSessionsErrorTotal+1, testutil.ToFloat64(metrics.SliSshdSessionsErrorsTotal), 0.1)
+		})
+	}
 }
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -10,26 +10,43 @@
 
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/gitlabnet/authorizedkeys"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/authorizedkeys"
 
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
+var (
+	supportedMACs = []string{
+		"hmac-sha2-256-etm@openssh.com",
+		"hmac-sha2-512-etm@openssh.com",
+		"hmac-sha2-256",
+		"hmac-sha2-512",
+		"hmac-sha1",
+	}
+
+	supportedKeyExchanges = []string{
+		"curve25519-sha256",
+		"curve25519-sha256@libssh.org",
+		"ecdh-sha2-nistp256",
+		"ecdh-sha2-nistp384",
+		"ecdh-sha2-nistp521",
+		"diffie-hellman-group14-sha256",
+		"diffie-hellman-group14-sha1",
+	}
+)
+
 type serverConfig struct {
 	cfg                  *config.Config
 	hostKeys             []ssh.Signer
+	hostKeyToCertMap     map[string]*ssh.Certificate
 	authorizedKeysClient *authorizedkeys.Client
 }
 
-func newServerConfig(cfg *config.Config) (*serverConfig, error) {
-	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
-	if err != nil {
-		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
-	}
+func parseHostKeys(keyFiles []string) []ssh.Signer {
+	var hostKeys []ssh.Signer
 
-	var hostKeys []ssh.Signer
-	for _, filename := range cfg.Server.HostKeyFiles {
+	for _, filename := range keyFiles {
 		keyRaw, err := os.ReadFile(filename)
 		if err != nil {
 			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("Failed to read host key")
@@ -43,11 +60,70 @@
 
 		hostKeys = append(hostKeys, key)
 	}
+
+	return hostKeys
+}
+
+func parseHostCerts(hostKeys []ssh.Signer, certFiles []string) map[string]*ssh.Certificate {
+	keyToCertMap := map[string]*ssh.Certificate{}
+	hostKeyIndex := make(map[string]int)
+
+	for index, hostKey := range hostKeys {
+		hostKeyIndex[string(hostKey.PublicKey().Marshal())] = index
+	}
+
+	for _, filename := range certFiles {
+		keyRaw, err := os.ReadFile(filename)
+		if err != nil {
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("failed to read host certificate")
+			continue
+		}
+		publicKey, _, _, _, err := ssh.ParseAuthorizedKey(keyRaw)
+		if err != nil {
+			log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("failed to parse host certificate")
+			continue
+		}
+
+		cert, ok := publicKey.(*ssh.Certificate)
+		if !ok {
+			log.WithFields(log.Fields{"filename": filename}).Warn("failed to decode host certificate")
+			continue
+		}
+
+		hostRawKey := string(cert.Key.Marshal())
+		index, found := hostKeyIndex[hostRawKey]
+		if found {
+			keyToCertMap[hostRawKey] = cert
+
+			certSigner, err := ssh.NewCertSigner(cert, hostKeys[index])
+			if err != nil {
+				log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("the host certificate doesn't match the host private key")
+				continue
+			}
+
+			hostKeys[index] = certSigner
+		} else {
+			log.WithFields(log.Fields{"filename": filename}).Warnf("no matching private key for certificate %s", filename)
+		}
+	}
+
+	return keyToCertMap
+}
+
+func newServerConfig(cfg *config.Config) (*serverConfig, error) {
+	authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+	}
+
+	hostKeys := parseHostKeys(cfg.Server.HostKeyFiles)
 	if len(hostKeys) == 0 {
 		return nil, fmt.Errorf("No host keys could be loaded, aborting")
 	}
 
-	return &serverConfig{cfg: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
+	hostKeyToCertMap := parseHostCerts(hostKeys, cfg.Server.HostCertFiles)
+
+	return &serverConfig{cfg: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys, hostKeyToCertMap: hostKeyToCertMap}, nil
 }
 
 func (s *serverConfig) getAuthKey(ctx context.Context, user string, key ssh.PublicKey) (*authorizedkeys.Response, error) {
@@ -84,6 +160,23 @@
 				},
 			}, nil
 		},
+		ServerVersion: "SSH-2.0-GitLab-SSHD",
+	}
+
+	if len(s.cfg.Server.MACs) > 0 {
+		sshCfg.MACs = s.cfg.Server.MACs
+	} else {
+		sshCfg.MACs = supportedMACs
+	}
+
+	if len(s.cfg.Server.KexAlgorithms) > 0 {
+		sshCfg.KeyExchanges = s.cfg.Server.KexAlgorithms
+	} else {
+		sshCfg.KeyExchanges = supportedKeyExchanges
+	}
+
+	if len(s.cfg.Server.Ciphers) > 0 {
+		sshCfg.Ciphers = s.cfg.Server.Ciphers
 	}
 
 	for _, key := range s.hostKeys {
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
--- a/internal/sshd/server_config_test.go
+++ b/internal/sshd/server_config_test.go
@@ -5,14 +5,15 @@
 	"crypto/dsa"
 	"crypto/rand"
 	"crypto/rsa"
+	"os"
 	"path"
 	"testing"
 
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 func TestNewServerConfigWithoutHosts(t *testing.T) {
@@ -22,6 +23,45 @@
 	require.Equal(t, "No host keys could be loaded, aborting", err.Error())
 }
 
+func TestHostKeyAndCerts(t *testing.T) {
+	testhelper.PrepareTestRootDir(t)
+
+	srvCfg := config.ServerConfig{
+		Listen:                  "127.0.0.1",
+		ConcurrentSessionsLimit: 1,
+		HostKeyFiles: []string{
+			path.Join(testhelper.TestRoot, "certs/valid/server.key"),
+		},
+		HostCertFiles: []string{
+			path.Join(testhelper.TestRoot, "certs/valid/server-cert.pub"),
+			path.Join(testhelper.TestRoot, "certs/valid/server2-cert.pub"),
+			path.Join(testhelper.TestRoot, "certs/invalid/server-cert.pub"),
+			path.Join(testhelper.TestRoot, "certs/invalid-path.key"),
+			path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+		},
+	}
+
+	cfg, err := newServerConfig(
+		&config.Config{GitlabUrl: "http://localhost", User: "user", Server: srvCfg},
+	)
+	require.NoError(t, err)
+
+	require.Len(t, cfg.hostKeys, 1)
+	require.Len(t, cfg.hostKeyToCertMap, 1)
+
+	// Check that the entry is pointing to the server's public key
+	data, err := os.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server.pub"))
+	require.NoError(t, err)
+
+	publicKey, _, _, _, err := ssh.ParseAuthorizedKey(data)
+	require.NoError(t, err)
+	require.NotNil(t, publicKey)
+	cert, ok := cfg.hostKeyToCertMap[string(publicKey.Marshal())]
+	require.True(t, ok)
+	require.NotNil(t, cert)
+	require.Equal(t, cert, cfg.hostKeys[0].PublicKey())
+}
+
 func TestFailedAuthorizedKeysClient(t *testing.T) {
 	_, err := newServerConfig(&config.Config{GitlabUrl: "ftp://localhost"})
 
@@ -80,6 +120,57 @@
 	}
 }
 
+func TestDefaultAlgorithms(t *testing.T) {
+	srvCfg := &serverConfig{cfg: &config.Config{}}
+	sshServerConfig := srvCfg.get(context.Background())
+
+	require.Equal(t, supportedMACs, sshServerConfig.MACs)
+	require.Equal(t, supportedKeyExchanges, sshServerConfig.KeyExchanges)
+	require.Nil(t, sshServerConfig.Ciphers)
+
+	sshServerConfig.SetDefaults()
+
+	require.Equal(t, supportedMACs, sshServerConfig.MACs)
+	require.Equal(t, supportedKeyExchanges, sshServerConfig.KeyExchanges)
+
+	defaultCiphers := []string{
+		"aes128-gcm@openssh.com",
+		"chacha20-poly1305@openssh.com",
+		"aes256-gcm@openssh.com",
+		"aes128-ctr",
+		"aes192-ctr",
+		"aes256-ctr",
+	}
+	require.Equal(t, defaultCiphers, sshServerConfig.Ciphers)
+}
+
+func TestCustomAlgorithms(t *testing.T) {
+	customMACs := []string{"hmac-sha2-512-etm@openssh.com"}
+	customKexAlgos := []string{"curve25519-sha256"}
+	customCiphers := []string{"aes256-gcm@openssh.com"}
+
+	srvCfg := &serverConfig{
+		cfg: &config.Config{
+			Server: config.ServerConfig{
+				MACs:          customMACs,
+				KexAlgorithms: customKexAlgos,
+				Ciphers:       customCiphers,
+			},
+		},
+	}
+	sshServerConfig := srvCfg.get(context.Background())
+
+	require.Equal(t, customMACs, sshServerConfig.MACs)
+	require.Equal(t, customKexAlgos, sshServerConfig.KeyExchanges)
+	require.Equal(t, customCiphers, sshServerConfig.Ciphers)
+
+	sshServerConfig.SetDefaults()
+
+	require.Equal(t, customMACs, sshServerConfig.MACs)
+	require.Equal(t, customKexAlgos, sshServerConfig.KeyExchanges)
+	require.Equal(t, customCiphers, sshServerConfig.Ciphers)
+}
+
 func rsaPublicKey(t *testing.T) ssh.PublicKey {
 	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
 	require.NoError(t, err)
diff --git a/internal/sshd/session.go b/internal/sshd/session.go
--- a/internal/sshd/session.go
+++ b/internal/sshd/session.go
@@ -5,15 +5,20 @@
 	"errors"
 	"fmt"
 	"reflect"
+	"time"
 
 	"gitlab.com/gitlab-org/labkit/log"
 	"golang.org/x/crypto/ssh"
+	grpccodes "google.golang.org/grpc/codes"
+	grpcstatus "google.golang.org/grpc/status"
 
-	shellCmd "gitlab.com/gitlab-org/gitlab-shell/cmd/gitlab-shell/command"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/readwriter"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/command/shared/disallowedcommand"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/sshenv"
+	shellCmd "gitlab.com/gitlab-org/gitlab-shell/v14/cmd/gitlab-shell/command"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/readwriter"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/disallowedcommand"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 )
 
 type session struct {
@@ -26,6 +31,7 @@
 	// State managed by the session
 	execCmd            string
 	gitProtocolVersion string
+	started            time.Time
 }
 
 type execRequest struct {
@@ -158,18 +164,29 @@
 
 	cmd, err := shellCmd.NewWithKey(s.gitlabKeyId, env, s.cfg, rw)
 	if err != nil {
-		if !errors.Is(err, disallowedcommand.Error) {
-			s.toStderr(ctx, "Failed to parse command: %v\n", err.Error())
+		if errors.Is(err, disallowedcommand.Error) {
+			s.toStderr(ctx, "ERROR: Unknown command: %v\n", s.execCmd)
+		} else {
+			s.toStderr(ctx, "ERROR: Failed to parse command: %v\n", err.Error())
 		}
-		s.toStderr(ctx, "Unknown command: %v\n", s.execCmd)
+
 		return 128, err
 	}
 
 	cmdName := reflect.TypeOf(cmd).String()
-	ctxlog.WithFields(log.Fields{"env": env, "command": cmdName}).Info("session: handleShell: executing command")
+
+	establishSessionDuration := time.Since(s.started).Seconds()
+	ctxlog.WithFields(log.Fields{
+		"env": env, "command": cmdName, "established_session_duration_s": establishSessionDuration,
+	}).Info("session: handleShell: executing command")
+	metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
 
 	if err := cmd.Execute(ctx); err != nil {
-		s.toStderr(ctx, "remote: ERROR: %v\n", err.Error())
+		grpcStatus := grpcstatus.Convert(err)
+		if grpcStatus.Code() != grpccodes.Internal {
+			s.toStderr(ctx, "ERROR: %v\n", grpcStatus.Message())
+		}
+
 		return 1, err
 	}
 
@@ -181,7 +198,7 @@
 func (s *session) toStderr(ctx context.Context, format string, args ...interface{}) {
 	out := fmt.Sprintf(format, args...)
 	log.WithContextFields(ctx, log.Fields{"stderr": out}).Debug("session: toStderr: output")
-	fmt.Fprint(s.channel.Stderr(), out)
+	console.DisplayWarningMessage(out, s.channel.Stderr())
 }
 
 func (s *session) exit(ctx context.Context, status uint32) {
diff --git a/internal/sshd/session_test.go b/internal/sshd/session_test.go
--- a/internal/sshd/session_test.go
+++ b/internal/sshd/session_test.go
@@ -11,8 +11,9 @@
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/console"
 )
 
 type fakeChannel struct {
@@ -160,14 +161,14 @@
 		{
 			desc:              "fails to parse command",
 			cmd:               `\`,
-			errMsg:            "Failed to parse command: Invalid SSH command: invalid command line string\nUnknown command: \\\n",
+			errMsg:            "ERROR: Failed to parse command: Invalid SSH command: invalid command line string\n",
 			gitlabKeyId:       "root",
 			expectedErrString: "Invalid SSH command: invalid command line string",
 			expectedExitCode:  128,
 		}, {
 			desc:              "specified command is unknown",
 			cmd:               "unknown-command",
-			errMsg:            "Unknown command: unknown-command\n",
+			errMsg:            "ERROR: Unknown command: unknown-command\n",
 			gitlabKeyId:       "root",
 			expectedErrString: "Disallowed command",
 			expectedExitCode:  128,
@@ -175,7 +176,7 @@
 			desc:              "fails to parse command",
 			cmd:               "discover",
 			gitlabKeyId:       "",
-			errMsg:            "remote: ERROR: Failed to get username: who='' is invalid\n",
+			errMsg:            "ERROR: Failed to get username: who='' is invalid\n",
 			expectedErrString: "Failed to get username: who='' is invalid",
 			expectedExitCode:  1,
 		}, {
@@ -208,7 +209,14 @@
 			}
 
 			require.Equal(t, tc.expectedExitCode, exitCode)
-			require.Equal(t, tc.errMsg, out.String())
+
+			formattedErr := &bytes.Buffer{}
+			if tc.errMsg != "" {
+				console.DisplayWarningMessage(tc.errMsg, formattedErr)
+				require.Equal(t, formattedErr.String(), out.String())
+			} else {
+				require.Equal(t, tc.errMsg, out.String())
+			}
 		})
 	}
 }
diff --git a/internal/sshd/sshd.go b/internal/sshd/sshd.go
--- a/internal/sshd/sshd.go
+++ b/internal/sshd/sshd.go
@@ -12,8 +12,10 @@
 	"github.com/pires/go-proxyproto"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/metrics"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/metrics"
 
 	"gitlab.com/gitlab-org/labkit/correlation"
 	"gitlab.com/gitlab-org/labkit/log"
@@ -26,7 +28,6 @@
 	StatusReady
 	StatusOnShutdown
 	StatusClosed
-	ProxyHeaderTimeout = 90 * time.Second
 )
 
 type Server struct {
@@ -97,7 +98,7 @@
 		sshListener = &proxyproto.Listener{
 			Listener:          sshListener,
 			Policy:            s.requirePolicy,
-			ReadHeaderTimeout: ProxyHeaderTimeout,
+			ReadHeaderTimeout: time.Duration(s.Config.Server.ProxyHeaderTimeout),
 		}
 
 		log.ContextLogger(ctx).Info("Proxy protocol is enabled")
@@ -146,19 +147,35 @@
 	return s.status
 }
 
+func contextWithValues(parent context.Context, nconn net.Conn) context.Context {
+	ctx := correlation.ContextWithCorrelation(parent, correlation.SafeRandomID())
+
+	// If we're dealing with a PROXY connection, register the original requester's IP
+	mconn, ok := nconn.(*proxyproto.Conn)
+	if ok {
+		ip := gitlabnet.ParseIP(mconn.Raw().RemoteAddr().String())
+		ctx = context.WithValue(ctx, client.OriginalRemoteIPContextKey{}, ip)
+	}
+
+	return ctx
+}
+
 func (s *Server) handleConn(ctx context.Context, nconn net.Conn) {
+	defer s.wg.Done()
+
 	metrics.SshdConnectionsInFlight.Inc()
 	defer metrics.SshdConnectionsInFlight.Dec()
 
-	remoteAddr := nconn.RemoteAddr().String()
-
-	defer s.wg.Done()
-	defer nconn.Close()
+	ctx, cancel := context.WithCancel(contextWithValues(ctx, nconn))
+	defer cancel()
+	go func() {
+		<-ctx.Done()
+		nconn.Close() // Close the connection when context is cancelled
+	}()
 
-	ctx, cancel := context.WithCancel(correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID()))
-	defer cancel()
-
+	remoteAddr := nconn.RemoteAddr().String()
 	ctxlog := log.WithContextFields(ctx, log.Fields{"remote_addr": remoteAddr})
+	ctxlog.Debug("server: handleConn: start")
 
 	// Prevent a panic in a single connection from taking out the whole server
 	defer func() {
@@ -169,38 +186,18 @@
 		}
 	}()
 
-	ctxlog.Info("server: handleConn: start")
-
-	sconn, chans, reqs, err := ssh.NewServerConn(nconn, s.serverConfig.get(ctx))
-	if err != nil {
-		ctxlog.WithError(err).Warn("server: handleConn: failed to initialize SSH connection")
-		return
-	}
-	go ssh.DiscardRequests(reqs)
-
-	started := time.Now()
-	var establishSessionDuration float64
-	conn := newConnection(s.Config, remoteAddr, sconn)
-	conn.handle(ctx, chans, func(ctx context.Context, channel ssh.Channel, requests <-chan *ssh.Request) error {
-		establishSessionDuration = time.Since(started).Seconds()
-		metrics.SshdSessionEstablishedDuration.Observe(establishSessionDuration)
-
+	conn := newConnection(s.Config, nconn)
+	conn.handle(ctx, s.serverConfig.get(ctx), func(sconn *ssh.ServerConn, channel ssh.Channel, requests <-chan *ssh.Request) error {
 		session := &session{
 			cfg:         s.Config,
 			channel:     channel,
 			gitlabKeyId: sconn.Permissions.Extensions["key-id"],
 			remoteAddr:  remoteAddr,
+			started:     time.Now(),
 		}
 
 		return session.handle(ctx, requests)
 	})
-
-	reason := sconn.Wait()
-	ctxlog.WithFields(log.Fields{
-		"duration_s":                   time.Since(started).Seconds(),
-		"establish_session_duration_s": establishSessionDuration,
-		"reason":                       reason,
-	}).Info("server: handleConn: done")
 }
 
 func (s *Server) requirePolicy(_ net.Addr) (proxyproto.Policy, error) {
diff --git a/internal/sshd/sshd_test.go b/internal/sshd/sshd_test.go
--- a/internal/sshd/sshd_test.go
+++ b/internal/sshd/sshd_test.go
@@ -15,9 +15,9 @@
 	"github.com/stretchr/testify/require"
 	"golang.org/x/crypto/ssh"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/config"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 const (
@@ -27,6 +27,7 @@
 
 var (
 	correlationId = ""
+	xForwardedFor = ""
 )
 
 func TestListenAndServe(t *testing.T) {
@@ -63,6 +64,10 @@
 		},
 		DestinationAddr: target,
 	}
+	xForwardedFor = "127.0.0.1"
+	defer func() {
+		xForwardedFor = "" // Cleanup for other test cases
+	}()
 
 	testCases := []struct {
 		desc        string
@@ -132,9 +137,9 @@
 				require.NoError(t, err)
 			}
 
-			sshConn, _, _, err := ssh.NewClientConn(conn, serverUrl, clientConfig(t))
+			sshConn, sshChans, sshRequs, err := ssh.NewClientConn(conn, serverUrl, clientConfig(t))
 			if sshConn != nil {
-				sshConn.Close()
+				defer sshConn.Close()
 			}
 
 			if tc.isRejected {
@@ -142,6 +147,10 @@
 				require.Regexp(t, "ssh: handshake failed", err.Error())
 			} else {
 				require.NoError(t, err)
+				client := ssh.NewClient(sshConn, sshChans, sshRequs)
+				defer client.Close()
+
+				holdSession(t, client)
 			}
 		})
 	}
@@ -222,6 +231,68 @@
 	require.Nil(t, s.Shutdown())
 }
 
+func TestClosingHangedConnections(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	s := setupServerWithContext(t, nil, ctx)
+
+	unauthenticatedRequestStatus := make(chan string)
+	completed := make(chan bool)
+
+	clientCfg := clientConfig(t)
+	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
+		unauthenticatedRequestStatus <- "authentication-started"
+		<-completed // Wait infinitely
+
+		return nil
+	}
+
+	go func() {
+		// Start an SSH connection that never ends
+		ssh.Dial("tcp", serverUrl, clientCfg)
+	}()
+
+	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
+
+	require.NoError(t, s.Shutdown())
+	cancel()
+	verifyStatus(t, s, StatusClosed)
+}
+
+func TestLoginGraceTime(t *testing.T) {
+	cfg := &config.Config{
+		Server: config.ServerConfig{
+			LoginGraceTime: config.YamlDuration(50 * time.Millisecond),
+		},
+	}
+	s := setupServerWithConfig(t, cfg)
+
+	unauthenticatedRequestStatus := make(chan string)
+	completed := make(chan bool)
+
+	clientCfg := clientConfig(t)
+	clientCfg.HostKeyCallback = func(_ string, _ net.Addr, _ ssh.PublicKey) error {
+		unauthenticatedRequestStatus <- "authentication-started"
+		<-completed // Wait infinitely
+
+		return nil
+	}
+
+	go func() {
+		// Start an SSH connection that never ends
+		ssh.Dial("tcp", serverUrl, clientCfg)
+	}()
+
+	require.Equal(t, "authentication-started", <-unauthenticatedRequestStatus)
+
+	// Shutdown the server and verify that it's closed
+	// If LoginGraceTime doesn't work, then the connection that runs infinitely, will stop it from closing.
+	// The close won't happen until the context is canceled like in TestClosingHangedConnections
+	require.NoError(t, s.Shutdown())
+	verifyStatus(t, s, StatusClosed)
+}
+
 func setupServer(t *testing.T) *Server {
 	t.Helper()
 
@@ -231,6 +302,12 @@
 func setupServerWithConfig(t *testing.T, cfg *config.Config) *Server {
 	t.Helper()
 
+	return setupServerWithContext(t, cfg, context.Background())
+}
+
+func setupServerWithContext(t *testing.T, cfg *config.Config, ctx context.Context) *Server {
+	t.Helper()
+
 	requests := []testserver.TestRequestHandler{
 		{
 			Path: "/api/v4/internal/authorized_keys",
@@ -238,6 +315,7 @@
 				correlationId = r.Header.Get("X-Request-Id")
 
 				require.NotEmpty(t, correlationId)
+				require.Equal(t, xForwardedFor, r.Header.Get("X-Forwarded-For"))
 
 				fmt.Fprint(w, `{"id": 1000, "key": "key"}`)
 			},
@@ -245,6 +323,7 @@
 			Path: "/api/v4/internal/discover",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				require.Equal(t, correlationId, r.Header.Get("X-Request-Id"))
+				require.Equal(t, xForwardedFor, r.Header.Get("X-Forwarded-For"))
 
 				fmt.Fprint(w, `{"id": 1000, "name": "Test User", "username": "test-user"}`)
 			},
@@ -265,13 +344,12 @@
 	cfg.User = user
 	cfg.Server.Listen = serverUrl
 	cfg.Server.ConcurrentSessionsLimit = 1
-	cfg.Server.ClientAliveIntervalSeconds = 15
 	cfg.Server.HostKeyFiles = []string{path.Join(testhelper.TestRoot, "certs/valid/server.key")}
 
 	s, err := NewServer(cfg)
 	require.NoError(t, err)
 
-	go func() { require.NoError(t, s.ListenAndServe(context.Background())) }()
+	go func() { require.NoError(t, s.ListenAndServe(ctx)) }()
 	t.Cleanup(func() { s.Shutdown() })
 
 	verifyStatus(t, s, StatusReady)
diff --git a/internal/sshenv/sshenv_test.go b/internal/sshenv/sshenv_test.go
--- a/internal/sshenv/sshenv_test.go
+++ b/internal/sshenv/sshenv_test.go
@@ -4,7 +4,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 )
 
 func TestNewFromEnv(t *testing.T) {
diff --git a/internal/testhelper/requesthandlers/requesthandlers.go b/internal/testhelper/requesthandlers/requesthandlers.go
--- a/internal/testhelper/requesthandlers/requesthandlers.go
+++ b/internal/testhelper/requesthandlers/requesthandlers.go
@@ -7,7 +7,7 @@
 
 	"github.com/stretchr/testify/require"
 
-	"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
+	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
 )
 
 func BuildDisallowedByApiHandlers(t *testing.T) []testserver.TestRequestHandler {
diff --git a/internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub b/internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub
@@ -0,0 +1,1 @@
+ssh-rsa-cert-v01@openssh.com AAAAB3NzaC1yc2EAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yql not_a_valid_cert.pub
diff --git a/internal/testhelper/testdata/testroot/certs/valid/ca b/internal/testhelper/testdata/testroot/certs/valid/ca
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/ca
@@ -0,0 +1,38 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAYEA3IUBN80/BJG5ut9U238FwIGAfMZQoOXYbFtiQZo+U+cdbGJlEGgV
++IToGN78YJypkbK8NiptMMOgWZ8eu6DCJMNxjPb7e3XYGrmHPeoK5Z9ioESDw0QSkxDIfw
+FoLSnx0+eiXS138ypENgLT/hOMl+fmroA74ZpvkvIpK+RoAo1qlWCWtA7NygkQkWRgnnAj
+4jkDKq9aT1iHGa9nQYLuTtfiX3l35/oOOMJFrDg7bb5EHnBsKewDoAxdqNbxp4W/sQyUF4
+NbOFLGzRavA6eNRfOx9UXcdy0v9+UgCmu92nbiPmNBo9wY3D9prwXAWetTM1B3ZCDk1hqI
+cXRg6pNBNC9e2Fubchs9TcrLaZpr4kgFvTekGcof1m2ozJTVlcIJlJpy95mp1uXwxTPbfX
+KGPvKu7NroR2avkAIfwRj5E/8HwrgDH2r/4uRPadRCx9hm38HI1n6S4YrrsmL9ffLW6SCf
+EG6c7+URDQr18Vq9YyxMmu09EK7VlDDX0JM0Uan5AAAFkA5fIlIOXyJSAAAAB3NzaC1yc2
+EAAAGBANyFATfNPwSRubrfVNt/BcCBgHzGUKDl2GxbYkGaPlPnHWxiZRBoFfiE6Bje/GCc
+qZGyvDYqbTDDoFmfHrugwiTDcYz2+3t12Bq5hz3qCuWfYqBEg8NEEpMQyH8BaC0p8dPnol
+0td/MqRDYC0/4TjJfn5q6AO+Gab5LyKSvkaAKNapVglrQOzcoJEJFkYJ5wI+I5AyqvWk9Y
+hxmvZ0GC7k7X4l95d+f6DjjCRaw4O22+RB5wbCnsA6AMXajW8aeFv7EMlBeDWzhSxs0Wrw
+OnjUXzsfVF3HctL/flIAprvdp24j5jQaPcGNw/aa8FwFnrUzNQd2Qg5NYaiHF0YOqTQTQv
+Xthbm3IbPU3Ky2maa+JIBb03pBnKH9ZtqMyU1ZXCCZSacveZqdbl8MUz231yhj7yruza6E
+dmr5ACH8EY+RP/B8K4Ax9q/+LkT2nUQsfYZt/ByNZ+kuGK67Ji/X3y1ukgnxBunO/lEQ0K
+9fFavWMsTJrtPRCu1ZQw19CTNFGp+QAAAAMBAAEAAAGACHnYRSPPc0aCpAsngNRODUss/B
+7HRJfxDKEqkqjyElmEyQCzL8FAbu/019fiTXhYEDCViWNyFPi/9hHmpYGVVMJqX+eyXNl3
+t/c/moKfbpoEuXJIuj2olRyFCFSug2XkVKfHlttDjAYo3waWzWJE+iXAuR5WruI3vacvK+
++4i7iRyzIOONeE02orx9ra19wplO1qEL7ysrANaVBToLH+pOspWVAa6sCywT2+XdM/fYVd
+qunZTncy4Hj5NJ8mZLEATfJKnT2v7C47fBjN+ylqpyTImBZxSfVyjrljcQXb9ExjAhVTjv
+tBuZdB1NPnok9cycwpg6aGXuZX2mSQWROhHM/r80kUzfxJpRDs/AqMWRZYC2k/kCKbXg7S
+1cuAwJ2SiH5jslekhbB8bCU3rL2SgUV4oZsqh5fb6ZsytXarbzX/8Kcmb4KGsjZ7wBD6Yu
+sJ05TkzC/HkOT3xTXwyzZpEldKucLClnY3Boq8pkO1EoUD8uPJNgSgukH9W5SleaIxAAAA
+wEzXR8Av4SxsgWW2pPVtQaeKhUKrid21RuPF5/7c4PZ4GlnhL3Fod4PwdD8yPjuIuI7s6/
+9HRxzi+vr616/BXMagGWPSZEQMaX6I/L5CSratN2Dk1jSYcH501GseILr+kIcZhe7HoEf2
+xbr8ByF88DXpeSdimIqMeVYTPGWac7oSf3Y5WHi9FUuJ4BEccu8bLIXWkGMK6yi/zJo1RQ
+u4aMzdMyzat0C2aeAm40HABdUv350K/H20Voj7zfhmlXvQ7wAAAMEA8a1oEPFL1+cAqfUD
+Jbx+KWyw/1kFBIpU93rk2qfJR593nMLebAk0l9qfhbvlN6GTNcGETjzGK1bHaD9G14c2IT
+bFcIoKmah6LygIlMGwdTMSWPPrczeIhMy6H0rJ2lDa208+nLwKqlFlMDYNpycL2Q1ZynnB
+fYqfRiUSDJcs+2jfTX0gA17NuSwqp6j/JlMm45tN3GK1neIVH+4PBazBXqZTzdfCfqJ9r5
+TWJw2i6CsSlCDAtO3uo+Pyj327RbNtAAAAwQDplpqK2+0+QiQB+LUeT0hfWp5/HmXJjfgm
+u+xIICJKPqOYwDvBWEHHssv7YS70dFJrbENJ66NZfdv+foDbQXrr10odbIntk9QoO4CS1g
+zd63kolFCLhbwkYos45CjJIPuzNDeiYIgsLEOQwnjHbp3HxAIywxtUPKj80YmfoogeidiD
+JNMwRoJfqlNziW1PDq0r8Zhw2lbyGZPI218ox7tsJ94BS4MFJfgASwO9qcDsaYz23sS8uQ
+BBbY6cCknC7T0AAAAUc3Rhbmh1QGpldC1hcm0ubG9jYWwBAgMEBQYH
+-----END OPENSSH PRIVATE KEY-----
diff --git a/internal/testhelper/testdata/testroot/certs/valid/ca.pub b/internal/testhelper/testdata/testroot/certs/valid/ca.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/ca.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDchQE3zT8Ekbm631TbfwXAgYB8xlCg5dhsW2JBmj5T5x1sYmUQaBX4hOgY3vxgnKmRsrw2Km0ww6BZnx67oMIkw3GM9vt7ddgauYc96grln2KgRIPDRBKTEMh/AWgtKfHT56JdLXfzKkQ2AtP+E4yX5+augDvhmm+S8ikr5GgCjWqVYJa0Ds3KCRCRZGCecCPiOQMqr1pPWIcZr2dBgu5O1+JfeXfn+g44wkWsODttvkQecGwp7AOgDF2o1vGnhb+xDJQXg1s4UsbNFq8Dp41F87H1Rdx3LS/35SAKa73aduI+Y0Gj3BjcP2mvBcBZ61MzUHdkIOTWGohxdGDqk0E0L17YW5tyGz1NystpmmviSAW9N6QZyh/WbajMlNWVwgmUmnL3manW5fDFM9t9coY+8q7s2uhHZq+QAh/BGPkT/wfCuAMfav/i5E9p1ELH2GbfwcjWfpLhiuuyYv198tbpIJ8Qbpzv5RENCvXxWr1jLEya7T0QrtWUMNfQkzRRqfk= test@test.example.org
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server-cert.pub b/internal/testhelper/testdata/testroot/certs/valid/server-cert.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server-cert.pub
@@ -0,0 +1,1 @@
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgNGUtD+qw0Xj5NU2uj4+4LoCWPcvXP54F9Adw/hWN5LAAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yqlAAAAAAAAAAAAAAACAAAABnNlcnZlcgAAAAAAAAAAAAAAAP//////////AAAAAAAAAAAAAAAAAAABlwAAAAdzc2gtcnNhAAAAAwEAAQAAAYEA3IUBN80/BJG5ut9U238FwIGAfMZQoOXYbFtiQZo+U+cdbGJlEGgV+IToGN78YJypkbK8NiptMMOgWZ8eu6DCJMNxjPb7e3XYGrmHPeoK5Z9ioESDw0QSkxDIfwFoLSnx0+eiXS138ypENgLT/hOMl+fmroA74ZpvkvIpK+RoAo1qlWCWtA7NygkQkWRgnnAj4jkDKq9aT1iHGa9nQYLuTtfiX3l35/oOOMJFrDg7bb5EHnBsKewDoAxdqNbxp4W/sQyUF4NbOFLGzRavA6eNRfOx9UXcdy0v9+UgCmu92nbiPmNBo9wY3D9prwXAWetTM1B3ZCDk1hqIcXRg6pNBNC9e2Fubchs9TcrLaZpr4kgFvTekGcof1m2ozJTVlcIJlJpy95mp1uXwxTPbfXKGPvKu7NroR2avkAIfwRj5E/8HwrgDH2r/4uRPadRCx9hm38HI1n6S4YrrsmL9ffLW6SCfEG6c7+URDQr18Vq9YyxMmu09EK7VlDDX0JM0Uan5AAABlAAAAAxyc2Etc2hhMi01MTIAAAGAapK5VzLDoRe88tnLORrH8VxLegTWPKGdn0k5Ye8tS5XUgd0N98gU669y4ErDNf0kPxlz40bjsfisEEtJ/N7m14IskCepScfZRh8w6QgPxbTOhmrc89xooqBUE50y5FU9hIIbzUrnEP+Dfu4IiFPToAguCa+KoAKiOX7lBQqEugV6uOWVZ2erPopEv+OiMLD8hXsuKKjQ+TomHZ/IjuXsFdXH7Vcl0VsPaAcxyCtDU7nCTJSTUoUZtMtwwpulp0e/zNmFrEn2Binz4jRaUlk3FM2fdbotviDQYeOY3npmxaWUvvQ/eKn0/DzUTAKAGr2LDa2XWnPhj51BS1XkNaUlnupdYmZ2Sok0R4U3bfVwokteREvAltGbXQSDtZwLS5NEY6vIdDrxpn5QRf9vGjqnc7piXxye9gcLne4YDUi24IhGyHrnWKCC0HjF7tuUhCOVKrqRdmHxRGWX3PlS8Xn6HHEPWU+YZnfT1V2W7LAFcDMozQbs4GPGzZdR3f3vCOJ8 server.pub
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server.pub b/internal/testhelper/testdata/testroot/certs/valid/server.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yql
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub b/internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub
@@ -0,0 +1,1 @@
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg3XO30wn4+lFxc5fLsanH4Pvu0OuKGHoR94zZjxx7qAEAAAADAQABAAABgQDK4MvpmiZ0cLS4+p3YEwcCwo4kVbJPTEeiIMuoEI7KJ3yqAjKJuns6VpnC6aRgu3LmuM4uBHcimQi415ClBOm4+tFiVsVNcAksA+QuU8rTEC6xPs96y1Y/qC+/WQn6+uKp1+fAspWsLpig3VXSTfq+YAcxZfTdO/6ck0kHVvxX096Ye7D0mdq2lWbGwSBlAbzU1wX/Znv5hLZD4DSG1cIjA9vQ/fJ9pwclZiS0qQ1VXdoIUAvL9tTAKj295VGT2NMGGYZQAQ0vXtM1YHOMAZ4XoPL1oVFjVEZoLRD58a6Dpe4hY8QKe6X1w7zU1rr5vVYrz7MTUa5pzsUSOeUTc3kyKJrExVkoLyBgYQq8qW9kYk/Ox9zHwTAKw9vwiIs2Mwe6nlxJ7cw4zykMsxPK4z9HkbZoTFWy3phuPFqs/WQoCvHTKNjWPab7UAameM6Dn0N83BF21tCTMWjkRCvtZVGIGZ7Y4cAiiN0OPzQWDasJ/IKVDRguZJRn3kgHCMYCgokAAAAAAAAAAAAAAAIAAAAHc2VydmVyMgAAAAAAAAAAAAAAAP//////////AAAAAAAAAAAAAAAAAAABlwAAAAdzc2gtcnNhAAAAAwEAAQAAAYEA3IUBN80/BJG5ut9U238FwIGAfMZQoOXYbFtiQZo+U+cdbGJlEGgV+IToGN78YJypkbK8NiptMMOgWZ8eu6DCJMNxjPb7e3XYGrmHPeoK5Z9ioESDw0QSkxDIfwFoLSnx0+eiXS138ypENgLT/hOMl+fmroA74ZpvkvIpK+RoAo1qlWCWtA7NygkQkWRgnnAj4jkDKq9aT1iHGa9nQYLuTtfiX3l35/oOOMJFrDg7bb5EHnBsKewDoAxdqNbxp4W/sQyUF4NbOFLGzRavA6eNRfOx9UXcdy0v9+UgCmu92nbiPmNBo9wY3D9prwXAWetTM1B3ZCDk1hqIcXRg6pNBNC9e2Fubchs9TcrLaZpr4kgFvTekGcof1m2ozJTVlcIJlJpy95mp1uXwxTPbfXKGPvKu7NroR2avkAIfwRj5E/8HwrgDH2r/4uRPadRCx9hm38HI1n6S4YrrsmL9ffLW6SCfEG6c7+URDQr18Vq9YyxMmu09EK7VlDDX0JM0Uan5AAABlAAAAAxyc2Etc2hhMi01MTIAAAGANOKG8Tq7kp9B5+CQEyb+mEatJOoQRV+4rpemWlfEw6TuVwQN2wSXc6XKBHzSG4NRnFkwk6GgiPLQEf4lBBKA8VYQnDKuhrHJlU4DFCRPw/aceHfCwNOruyJmuf91W3yEO/kYAd6EhkQiW/K3ky7BuXCqR34T2fBZSCeYhNcXWxhEMLoAuj0kEdX+YMNBmiPtinPE13KMFGyIVBm/ojgSZa8j4WnhDcK0cWv0OSGTgJF6q3hENCWRz2E1HroKUiABOy5Nca6gPVAi4OTd7gwER8eh9MngVHYorAJ3N9HjUh640SbL3zCC8f/lqIztqsHY0u3olsQ0gLXpFain+430HeyJlmVlsDZgQKRb90Mm1viSCKvHGpmVDYMimE9y0DCQS1i0yRGF1uSIPtuQ0NCbhS/HPKsT3nYgGCEuoB8aGOu3aGB/tmUkYXW+pwXRKqw0f/zX088XWYWvA+AR4hmmr6DDMnf/4EHgJp3xHTEwOBHCVj69xvlOawBNlL2X0b2p server2.pub
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server2.key b/internal/testhelper/testdata/testroot/certs/valid/server2.key
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server2.key
@@ -0,0 +1,38 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAYEAyuDL6ZomdHC0uPqd2BMHAsKOJFWyT0xHoiDLqBCOyid8qgIyibp7
+OlaZwumkYLty5rjOLgR3IpkIuNeQpQTpuPrRYlbFTXAJLAPkLlPK0xAusT7PestWP6gvv1
+kJ+vriqdfnwLKVrC6YoN1V0k36vmAHMWX03Tv+nJNJB1b8V9PemHuw9JnatpVmxsEgZQG8
+1NcF/2Z7+YS2Q+A0htXCIwPb0P3yfacHJWYktKkNVV3aCFALy/bUwCo9veVRk9jTBhmGUA
+ENL17TNWBzjAGeF6Dy9aFRY1RGaC0Q+fGug6XuIWPECnul9cO81Na6+b1WK8+zE1Guac7F
+EjnlE3N5MiiaxMVZKC8gYGEKvKlvZGJPzsfcx8EwCsPb8IiLNjMHup5cSe3MOM8pDLMTyu
+M/R5G2aExVst6YbjxarP1kKArx0yjY1j2m+1AGpnjOg59DfNwRdtbQkzFo5EQr7WVRiBme
+2OHAIojdDj80Fg2rCfyClQ0YLmSUZ95IBwjGAoKJAAAFkBOZmjETmZoxAAAAB3NzaC1yc2
+EAAAGBAMrgy+maJnRwtLj6ndgTBwLCjiRVsk9MR6Igy6gQjsonfKoCMom6ezpWmcLppGC7
+cua4zi4EdyKZCLjXkKUE6bj60WJWxU1wCSwD5C5TytMQLrE+z3rLVj+oL79ZCfr64qnX58
+CylawumKDdVdJN+r5gBzFl9N07/pyTSQdW/FfT3ph7sPSZ2raVZsbBIGUBvNTXBf9me/mE
+tkPgNIbVwiMD29D98n2nByVmJLSpDVVd2ghQC8v21MAqPb3lUZPY0wYZhlABDS9e0zVgc4
+wBnheg8vWhUWNURmgtEPnxroOl7iFjxAp7pfXDvNTWuvm9VivPsxNRrmnOxRI55RNzeTIo
+msTFWSgvIGBhCrypb2RiT87H3MfBMArD2/CIizYzB7qeXEntzDjPKQyzE8rjP0eRtmhMVb
+LemG48Wqz9ZCgK8dMo2NY9pvtQBqZ4zoOfQ3zcEXbW0JMxaOREK+1lUYgZntjhwCKI3Q4/
+NBYNqwn8gpUNGC5klGfeSAcIxgKCiQAAAAMBAAEAAAGAUxojzMt85wNns8HMuD6LB6FkEh
+QcVwka6plecrhdlQb5tLXzt6DwayQgFcwYrhr6ZPHcWtMvbbeb8AM017OcfU4YSJzccuzq
+hOIPLL7b/PrK9YWR/W2fJbIh5NJ3GRx9ji7HWpKMZpwrnvEq/1s705GIQL7Pv3Ocxsw6BM
+ynzt4VdwZrpLYE9fdawx1GxLkifViat1Rmgf3PnxwOyBB1Vlx1RTVQiBHMBpDBhlMdCBPK
+hM8tFd5EpXZoFgoCEXqlssIptaf0zUZAgeES31GwNrP7+n6SlL+xZxbs7ykWKjA1ibYDTE
+fLIojOQCOgnIFaDFbbgUiqxoYg1SAr2SRPOjopc5EXSt5kfCdQk3I5MKoSm2INNuwBqprI
+/BL0Do3VowAQkxjXJUWit9RR0wS7FiA54WJqOrfU+2ChRooVUvtt0i/0y9tJMr6+PJYawZ
+uLwQ4DXs3UNVFKdopyh+zcLht+1xIZO6VrMteXejVhcz8UnRQE7leCdOvCYbBX87eRAAAA
+wFX+Q0Cp8SD3Pf607nq0GNYgY+vQR/zqqTnIlYt3kKt0jP6TuvkOUnaANOt29W17LxIR1U
+tZxFfyktrT4/RiVRP+QWvdLS32IN3mpCPt+J0oKujlSWrUT0SJgd7ZLePJIOscjuFqW72M
+dNV7aCNIisc2QgJbp5EwCNTFxQmdqryk9Pd80kWSSAwWQ5A6jXSrLEAdaFTa9kF/o3DGNe
+E/6HOTnt7BFvdE1hetYFnUTR1vqjD21Pi3d5rnePYUOGI1nAAAAMEA9fDwdHOCjosfA1ri
+pWZDYYkR5JaxrCFZC5h2LcYL01aCutISH07Z5gmnAVM+CfHkihck9wiGDJuJNTo1mYR1pg
+aFgoIFg8LjZzBConlSnHTPYkIYHvYFaE9T4PIS8yjlHjaDn59P06nHRNa5W/4vvhGK8eEn
+hpBCQ68huqMZyCH9az6BYR1TasHY7GMbIuMXpswXt9tWRzXpuvQq0BFThNelviTw3JNFhC
+cdR2+2wMCErnT0w1Y9fd+8SIHL3OdVAAAAwQDTLPokbpKxlOQOdKMGgn0CZnhiAiLMKp6k
+ZQmqNjVEdiOkLWvmoBYMxW93n6uCpyc7p9R+++xXWvfIH7o4fCpiIcMKQtd7Ikp4s4uC6q
+xP0QOtGui5wac/iBgd0QB7ZlRh0WNIRS0ej5sn3wHz7es5eq1F/auGhxR6i5N0EhB/qI72
+lFgWtRJsIxi0tm424K6XWEjjj7fe7k9qQ712BVOvvhZp1OK/qfsOl6lj7VPXtcVPr9+6Aw
+MsHc/xbLoyRmUAAAAUc3Rhbmh1QGpldC1hcm0ubG9jYWwBAgMEBQYH
+-----END OPENSSH PRIVATE KEY-----
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server2.pub b/internal/testhelper/testdata/testroot/certs/valid/server2.pub
new file mode 100644
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server2.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDK4MvpmiZ0cLS4+p3YEwcCwo4kVbJPTEeiIMuoEI7KJ3yqAjKJuns6VpnC6aRgu3LmuM4uBHcimQi415ClBOm4+tFiVsVNcAksA+QuU8rTEC6xPs96y1Y/qC+/WQn6+uKp1+fAspWsLpig3VXSTfq+YAcxZfTdO/6ck0kHVvxX096Ye7D0mdq2lWbGwSBlAbzU1wX/Znv5hLZD4DSG1cIjA9vQ/fJ9pwclZiS0qQ1VXdoIUAvL9tTAKj295VGT2NMGGYZQAQ0vXtM1YHOMAZ4XoPL1oVFjVEZoLRD58a6Dpe4hY8QKe6X1w7zU1rr5vVYrz7MTUa5pzsUSOeUTc3kyKJrExVkoLyBgYQq8qW9kYk/Ox9zHwTAKw9vwiIs2Mwe6nlxJ7cw4zykMsxPK4z9HkbZoTFWy3phuPFqs/WQoCvHTKNjWPab7UAameM6Dn0N83BF21tCTMWjkRCvtZVGIGZ7Y4cAiiN0OPzQWDasJ/IKVDRguZJRn3kgHCMYCgok=
diff --git a/internal/testhelper/testdata/testroot/config.yml b/internal/testhelper/testdata/testroot/config.yml
--- a/internal/testhelper/testdata/testroot/config.yml
+++ b/internal/testhelper/testdata/testroot/config.yml
@@ -0,0 +1,4 @@
+sshd:
+  grace_period: 10
+  client_alive_interval: 1m
+  proxy_header_timeout: 500ms
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1669161776 -3600
#      Wed Nov 23 01:02:56 2022 +0100
# Branch heptapod
# Node ID 61e96ffd4b49d8579dfda6a058fb4b62e7362183
# Parent  c6529456e762c07071757b58ff7e1cc87b6947da
Added tag heptapod-14.10.0 for changeset c6529456e762

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -51,3 +51,4 @@
 54e1067b740ecd4e2af0b52e4b2472366aeeb466 heptapod-14.7.0
 cfb031a1e9de24b88498850cff796c224ba48edd heptapod-14.7.1
 cf818cb4e4fe5440a953da19e2e8650bddbf45d5 heptapod-14.9.0
+c6529456e762c07071757b58ff7e1cc87b6947da heptapod-14.10.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1678698223 -3600
#      Mon Mar 13 10:03:43 2023 +0100
# Branch heptapod-stable
# Node ID afce5deb7f84e3e4259b43e9f1eee78f0df83e8d
# Parent  f8f9fec42f926a6cee1f1b54a90d371e96c44d77
# Parent  61e96ffd4b49d8579dfda6a058fb4b62e7362183
heptapod#731: making 0.35 the new stable series

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -51,3 +51,4 @@
 54e1067b740ecd4e2af0b52e4b2472366aeeb466 heptapod-14.7.0
 cfb031a1e9de24b88498850cff796c224ba48edd heptapod-14.7.1
 cf818cb4e4fe5440a953da19e2e8650bddbf45d5 heptapod-14.9.0
+c6529456e762c07071757b58ff7e1cc87b6947da heptapod-14.10.0
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v14.10.0
+
+- Implement Push Auth support for 2FA verification !454
+
 v14.9.0
 
 - Update LabKit library to v1.16.0 !668
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 14.10.0
+
+- Bump to upstream GitLab Shell v14.10.0 (GitLab 15.3 and 15.4 series)
+
 ## 14.9.0
 
 - Bump to upstream GitLab Shell v14.9.0 (GitLab 15.2 series)
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-14.9.0
+14.10.0
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.9.0
+14.10.0
diff --git a/internal/command/twofactorverify/twofactorverify.go b/internal/command/twofactorverify/twofactorverify.go
--- a/internal/command/twofactorverify/twofactorverify.go
+++ b/internal/command/twofactorverify/twofactorverify.go
@@ -4,6 +4,7 @@
 	"context"
 	"fmt"
 	"io"
+	"time"
 
 	"gitlab.com/gitlab-org/labkit/log"
 
@@ -13,6 +14,11 @@
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
 )
 
+const (
+	timeout = 30 * time.Second
+	prompt  = "OTP: "
+)
+
 type Command struct {
 	Config     *config.Config
 	Args       *commandargs.Shell
@@ -20,25 +26,51 @@
 }
 
 func (c *Command) Execute(ctx context.Context) error {
-	ctxlog := log.ContextLogger(ctx)
-	ctxlog.Info("twofactorverify: execute: waiting for user input")
-	otp := c.getOTP(ctx)
-
-	ctxlog.Info("twofactorverify: execute: verifying entered OTP")
-	err := c.verifyOTP(ctx, otp)
+	client, err := twofactorverify.NewClient(c.Config)
 	if err != nil {
-		ctxlog.WithError(err).Error("twofactorverify: execute: OTP verification failed")
 		return err
 	}
 
-	ctxlog.WithError(err).Info("twofactorverify: execute: OTP verified")
+	ctx, cancel := context.WithTimeout(ctx, timeout)
+	defer cancel()
+
+	fmt.Fprint(c.ReadWriter.Out, prompt)
+
+	resultCh := make(chan string)
+	go func() {
+		err := client.PushAuth(ctx, c.Args)
+		if err == nil {
+			resultCh <- "OTP has been validated by Push Authentication. Git operations are now allowed."
+		}
+	}()
+
+	go func() {
+		answer, err := c.getOTP(ctx)
+		if err != nil {
+			resultCh <- formatErr(err)
+		}
+
+		if err := client.VerifyOTP(ctx, c.Args, answer); err != nil {
+			resultCh <- formatErr(err)
+		} else {
+			resultCh <- "OTP validation successful. Git operations are now allowed."
+		}
+	}()
+
+	var message string
+	select {
+	case message = <-resultCh:
+	case <-ctx.Done():
+		message = formatErr(ctx.Err())
+	}
+
+	log.WithContextFields(ctx, log.Fields{"message": message}).Info("Two factor verify command finished")
+	fmt.Fprintf(c.ReadWriter.Out, "\n%v\n", message)
+
 	return nil
 }
 
-func (c *Command) getOTP(ctx context.Context) string {
-	prompt := "OTP: "
-	fmt.Fprint(c.ReadWriter.Out, prompt)
-
+func (c *Command) getOTP(ctx context.Context) (string, error) {
 	var answer string
 	otpLength := int64(64)
 	reader := io.LimitReader(c.ReadWriter.In, otpLength)
@@ -46,21 +78,13 @@
 		log.ContextLogger(ctx).WithError(err).Debug("twofactorverify: getOTP: Failed to get user input")
 	}
 
-	return answer
-}
-
-func (c *Command) verifyOTP(ctx context.Context, otp string) error {
-	client, err := twofactorverify.NewClient(c.Config)
-	if err != nil {
-		return err
+	if answer == "" {
+		return "", fmt.Errorf("OTP cannot be blank.")
 	}
 
-	err = client.VerifyOTP(ctx, c.Args, otp)
-	if err == nil {
-		fmt.Fprint(c.ReadWriter.Out, "\nOTP validation successful. Git operations are now allowed.\n")
-	} else {
-		fmt.Fprintf(c.ReadWriter.Out, "\nOTP validation failed.\n%v\n", err)
-	}
+	return answer, nil
+}
 
-	return nil
+func formatErr(err error) string {
+	return fmt.Sprintf("OTP validation failed: %v", err)
 }
diff --git a/internal/command/twofactorverify/twofactorverify_test.go b/internal/command/twofactorverify/twofactorverify_test.go
--- a/internal/command/twofactorverify/twofactorverify_test.go
+++ b/internal/command/twofactorverify/twofactorverify_test.go
@@ -17,10 +17,20 @@
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/twofactorverify"
 )
 
+type blockingReader struct{}
+
+func (*blockingReader) Read([]byte) (int, error) {
+	waitInfinitely := make(chan struct{})
+	<-waitInfinitely
+
+	return 0, nil
+}
+
 func setup(t *testing.T) []testserver.TestRequestHandler {
+	waitInfinitely := make(chan struct{})
 	requests := []testserver.TestRequestHandler{
 		{
-			Path: "/api/v4/internal/two_factor_otp_check",
+			Path: "/api/v4/internal/two_factor_manual_otp_check",
 			Handler: func(w http.ResponseWriter, r *http.Request) {
 				b, err := io.ReadAll(r.Body)
 				defer r.Body.Close()
@@ -31,11 +41,13 @@
 				require.NoError(t, json.Unmarshal(b, &requestBody))
 
 				switch requestBody.KeyId {
-				case "1":
+				case "verify_via_otp", "verify_via_otp_with_push_error":
 					body := map[string]interface{}{
 						"success": true,
 					}
 					json.NewEncoder(w).Encode(body)
+				case "wait_infinitely":
+					<-waitInfinitely
 				case "error":
 					body := map[string]interface{}{
 						"success": false,
@@ -47,15 +59,36 @@
 				}
 			},
 		},
+		{
+			Path: "/api/v4/internal/two_factor_push_otp_check",
+			Handler: func(w http.ResponseWriter, r *http.Request) {
+				b, err := io.ReadAll(r.Body)
+				defer r.Body.Close()
+
+				require.NoError(t, err)
+
+				var requestBody *twofactorverify.RequestBody
+				require.NoError(t, json.Unmarshal(b, &requestBody))
+
+				switch requestBody.KeyId {
+				case "verify_via_push":
+					body := map[string]interface{}{
+						"success": true,
+					}
+					json.NewEncoder(w).Encode(body)
+				case "verify_via_otp_with_push_error":
+					w.WriteHeader(http.StatusInternalServerError)
+				default:
+					<-waitInfinitely
+				}
+			},
+		},
 	}
 
 	return requests
 }
 
-const (
-	question    = "OTP: \n"
-	errorHeader = "OTP validation failed.\n"
-)
+const errorHeader = "OTP validation failed: "
 
 func TestExecute(t *testing.T) {
 	requests := setup(t)
@@ -65,46 +98,61 @@
 	testCases := []struct {
 		desc           string
 		arguments      *commandargs.Shell
-		answer         string
+		input          io.Reader
 		expectedOutput string
 	}{
 		{
-			desc:      "With a known key id",
-			arguments: &commandargs.Shell{GitlabKeyId: "1"},
-			answer:    "123456\n",
-			expectedOutput: question +
-				"OTP validation successful. Git operations are now allowed.\n",
+			desc:           "Verify via OTP",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_otp"},
+			expectedOutput: "OTP validation successful. Git operations are now allowed.\n",
+		},
+		{
+			desc:           "Verify via OTP",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_otp_with_push_error"},
+			expectedOutput: "OTP validation successful. Git operations are now allowed.\n",
+		},
+		{
+			desc:           "Verify via push authentication",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_push"},
+			input:          &blockingReader{},
+			expectedOutput: "OTP has been validated by Push Authentication. Git operations are now allowed.\n",
+		},
+		{
+			desc:           "With an empty OTP",
+			arguments:      &commandargs.Shell{GitlabKeyId: "verify_via_otp"},
+			input:          bytes.NewBufferString("\n"),
+			expectedOutput: errorHeader + "OTP cannot be blank.\n",
 		},
 		{
 			desc:           "With bad response",
 			arguments:      &commandargs.Shell{GitlabKeyId: "-1"},
-			answer:         "123456\n",
-			expectedOutput: question + errorHeader + "Parsing failed\n",
+			expectedOutput: errorHeader + "Parsing failed\n",
 		},
 		{
 			desc:           "With API returns an error",
 			arguments:      &commandargs.Shell{GitlabKeyId: "error"},
-			answer:         "yes\n",
-			expectedOutput: question + errorHeader + "error message\n",
+			expectedOutput: errorHeader + "error message\n",
 		},
 		{
 			desc:           "With API fails",
 			arguments:      &commandargs.Shell{GitlabKeyId: "broken"},
-			answer:         "yes\n",
-			expectedOutput: question + errorHeader + "Internal API error (500)\n",
+			expectedOutput: errorHeader + "Internal API error (500)\n",
 		},
 		{
 			desc:           "With missing arguments",
 			arguments:      &commandargs.Shell{},
-			answer:         "yes\n",
-			expectedOutput: question + errorHeader + "who='' is invalid\n",
+			expectedOutput: errorHeader + "who='' is invalid\n",
 		},
 	}
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
 			output := &bytes.Buffer{}
-			input := bytes.NewBufferString(tc.answer)
+
+			input := tc.input
+			if input == nil {
+				input = bytes.NewBufferString("123456\n")
+			}
 
 			cmd := &Command{
 				Config:     &config.Config{GitlabUrl: url},
@@ -115,7 +163,29 @@
 			err := cmd.Execute(context.Background())
 
 			require.NoError(t, err)
-			require.Equal(t, tc.expectedOutput, output.String())
+			require.Equal(t, prompt+"\n"+tc.expectedOutput, output.String())
 		})
 	}
 }
+
+func TestCanceledContext(t *testing.T) {
+	requests := setup(t)
+
+	output := &bytes.Buffer{}
+
+	url := testserver.StartSocketHttpServer(t, requests)
+	cmd := &Command{
+		Config:     &config.Config{GitlabUrl: url},
+		Args:       &commandargs.Shell{GitlabKeyId: "wait_infinitely"},
+		ReadWriter: &readwriter.ReadWriter{Out: output, In: &bytes.Buffer{}},
+	}
+
+	ctx, cancel := context.WithCancel(context.Background())
+
+	errCh := make(chan error)
+	go func() { errCh <- cmd.Execute(ctx) }()
+	cancel()
+
+	require.NoError(t, <-errCh)
+	require.Equal(t, prompt+"\n"+errorHeader+"context canceled\n", output.String())
+}
diff --git a/internal/gitlabnet/twofactorverify/client.go b/internal/gitlabnet/twofactorverify/client.go
--- a/internal/gitlabnet/twofactorverify/client.go
+++ b/internal/gitlabnet/twofactorverify/client.go
@@ -26,7 +26,7 @@
 type RequestBody struct {
 	KeyId      string `json:"key_id,omitempty"`
 	UserId     int64  `json:"user_id,omitempty"`
-	OTPAttempt string `json:"otp_attempt"`
+	OTPAttempt string `json:"otp_attempt,omitempty"`
 }
 
 func NewClient(config *config.Config) (*Client, error) {
@@ -44,7 +44,22 @@
 		return err
 	}
 
-	response, err := c.client.Post(ctx, "/two_factor_otp_check", requestBody)
+	response, err := c.client.Post(ctx, "/two_factor_manual_otp_check", requestBody)
+	if err != nil {
+		return err
+	}
+	defer response.Body.Close()
+
+	return parse(response)
+}
+
+func (c *Client) PushAuth(ctx context.Context, args *commandargs.Shell) error {
+	requestBody, err := c.getRequestBody(ctx, args, "")
+	if err != nil {
+		return err
+	}
+
+	response, err := c.client.Post(ctx, "/two_factor_push_otp_check", requestBody)
 	if err != nil {
 		return err
 	}
diff --git a/internal/gitlabnet/twofactorverify/client_test.go b/internal/gitlabnet/twofactorverify/client_test.go
--- a/internal/gitlabnet/twofactorverify/client_test.go
+++ b/internal/gitlabnet/twofactorverify/client_test.go
@@ -17,49 +17,55 @@
 )
 
 func initialize(t *testing.T) []testserver.TestRequestHandler {
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		b, err := io.ReadAll(r.Body)
+		defer r.Body.Close()
+
+		require.NoError(t, err)
+
+		var requestBody *RequestBody
+		require.NoError(t, json.Unmarshal(b, &requestBody))
+
+		switch requestBody.KeyId {
+		case "0":
+			body := map[string]interface{}{
+				"success": true,
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		case "1":
+			body := map[string]interface{}{
+				"success": false,
+				"message": "error message",
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		case "2":
+			w.WriteHeader(http.StatusForbidden)
+			body := &client.ErrorResponse{
+				Message: "Not allowed!",
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		case "3":
+			w.Write([]byte("{ \"message\": \"broken json!\""))
+		case "4":
+			w.WriteHeader(http.StatusForbidden)
+		}
+
+		if requestBody.UserId == 1 {
+			body := map[string]interface{}{
+				"success": true,
+			}
+			require.NoError(t, json.NewEncoder(w).Encode(body))
+		}
+	}
+
 	requests := []testserver.TestRequestHandler{
 		{
-			Path: "/api/v4/internal/two_factor_otp_check",
-			Handler: func(w http.ResponseWriter, r *http.Request) {
-				b, err := io.ReadAll(r.Body)
-				defer r.Body.Close()
-
-				require.NoError(t, err)
-
-				var requestBody *RequestBody
-				require.NoError(t, json.Unmarshal(b, &requestBody))
-
-				switch requestBody.KeyId {
-				case "0":
-					body := map[string]interface{}{
-						"success": true,
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "1":
-					body := map[string]interface{}{
-						"success": false,
-						"message": "error message",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "2":
-					w.WriteHeader(http.StatusForbidden)
-					body := &client.ErrorResponse{
-						Message: "Not allowed!",
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				case "3":
-					w.Write([]byte("{ \"message\": \"broken json!\""))
-				case "4":
-					w.WriteHeader(http.StatusForbidden)
-				}
-
-				if requestBody.UserId == 1 {
-					body := map[string]interface{}{
-						"success": true,
-					}
-					require.NoError(t, json.NewEncoder(w).Encode(body))
-				}
-			},
+			Path:    "/api/v4/internal/two_factor_manual_otp_check",
+			Handler: handler,
+		},
+		{
+			Path:    "/api/v4/internal/two_factor_push_otp_check",
+			Handler: handler,
 		},
 		{
 			Path: "/api/v4/internal/discover",
@@ -140,6 +146,57 @@
 	}
 }
 
+func TestVerifyPush(t *testing.T) {
+	client := setup(t)
+
+	args := &commandargs.Shell{GitlabKeyId: "0"}
+	err := client.PushAuth(context.Background(), args)
+	require.NoError(t, err)
+}
+
+func TestErrorMessagePush(t *testing.T) {
+	client := setup(t)
+
+	args := &commandargs.Shell{GitlabKeyId: "1"}
+	err := client.PushAuth(context.Background(), args)
+	require.Equal(t, "error message", err.Error())
+}
+
+func TestErrorResponsesPush(t *testing.T) {
+	client := setup(t)
+
+	testCases := []struct {
+		desc          string
+		fakeId        string
+		expectedError string
+	}{
+		{
+			desc:          "A response with an error message",
+			fakeId:        "2",
+			expectedError: "Not allowed!",
+		},
+		{
+			desc:          "A response with bad JSON",
+			fakeId:        "3",
+			expectedError: "Parsing failed",
+		},
+		{
+			desc:          "An error response without message",
+			fakeId:        "4",
+			expectedError: "Internal API error (403)",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			args := &commandargs.Shell{GitlabKeyId: tc.fakeId}
+			err := client.PushAuth(context.Background(), args)
+
+			require.EqualError(t, err, tc.expectedError)
+		})
+	}
+}
+
 func setup(t *testing.T) *Client {
 	requests := initialize(t)
 	url := testserver.StartSocketHttpServer(t, requests)
diff --git a/internal/sshd/connection_test.go b/internal/sshd/connection_test.go
--- a/internal/sshd/connection_test.go
+++ b/internal/sshd/connection_test.go
@@ -223,12 +223,12 @@
 		{"disallowed command", disallowedcommand.Error},
 		{"not our ref", grpcstatus.Error(grpccodes.Internal, `rpc error: code = Internal desc = cmd wait: exit status 128, stderr: "fatal: git upload-pack: not our ref 9106d18f6a1b8022f6517f479696f3e3ea5e68c1"`)},
 	} {
-		ignoredError := ignoredError // Capture range variable, see "Race on loop counter" in https://go.dev/doc/articles/race_detector
 		t.Run(ignoredError.desc, func(t *testing.T) {
 			conn, chans = setup(1, newChannel)
+			ignored := ignoredError.err
 			conn.handleRequests(context.Background(), nil, chans, func(*ssh.ServerConn, ssh.Channel, <-chan *ssh.Request) error {
 				close(chans)
-				return ignoredError.err
+				return ignored
 			})
 
 			require.InDelta(t, initialSessionsTotal+2+float64(i), testutil.ToFloat64(metrics.SliSshdSessionsTotal), 0.1)
diff --git a/spec/gitlab_shell_two_factor_verify_spec.rb b/spec/gitlab_shell_two_factor_verify_spec.rb
--- a/spec/gitlab_shell_two_factor_verify_spec.rb
+++ b/spec/gitlab_shell_two_factor_verify_spec.rb
@@ -11,71 +11,115 @@
       'SSH_ORIGINAL_COMMAND' => '2fa_verify' }
   end
 
+  let(:correct_otp) { '123456' }
+
   before(:context) do
     write_config('gitlab_url' => "http+unix://#{CGI.escape(tmp_socket_path)}")
   end
 
   def mock_server(server)
-    server.mount_proc('/api/v4/internal/two_factor_otp_check') do |req, res|
+    server.mount_proc('/api/v4/internal/two_factor_manual_otp_check') do |req, res|
+      res.content_type = 'application/json'
+      res.status = 200
+
+      params = JSON.parse(req.body)
+
+      res.body = if params['otp_attempt'] == correct_otp
+                   { success: true }.to_json
+                 else
+                   { success: false, message: 'boom!' }.to_json
+                 end
+    end
+
+    server.mount_proc('/api/v4/internal/two_factor_push_otp_check') do |req, res|
       res.content_type = 'application/json'
       res.status = 200
 
       params = JSON.parse(req.body)
-      key_id = params['key_id'] || params['user_id'].to_s
+      id = params['key_id'] || params['user_id'].to_s
 
-      if key_id == '100'
+      if id == '100'
+        res.body = { success: false, message: 'boom!' }.to_json
+      else
         res.body = { success: true }.to_json
-      else
-        res.body = { success: false, message: 'boom!' }.to_json
       end
     end
 
-    server.mount_proc('/api/v4/internal/discover') do |_, res|
+    server.mount_proc('/api/v4/internal/discover') do |req, res|
       res.status = 200
       res.content_type = 'application/json'
-      res.body = { id: 100, name: 'Some User', username: 'someuser' }.to_json
+
+      if req.query['username'] == 'someone'
+        res.body = { id: 100, name: 'Some User', username: 'someuser' }.to_json
+      else
+        res.body = { id: 101, name: 'Another User', username: 'another' }.to_json
+      end
     end
   end
 
-  describe 'command' do
+  describe 'entering OTP manually' do
+    let(:cmd) { "#{gitlab_shell_path} key-100" }
+
     context 'when key is provided' do
-      let(:cmd) { "#{gitlab_shell_path} key-100" }
-
-      it 'prints a successful verification message' do
-        verify_successful_verification!(cmd)
+      it 'asks a user for a correct OTP' do
+        verify_successful_otp_verification!(cmd)
       end
     end
 
     context 'when username is provided' do
       let(:cmd) { "#{gitlab_shell_path} username-someone" }
 
-      it 'prints a successful verification message' do
-        verify_successful_verification!(cmd)
+      it 'asks a user for a correct OTP' do
+        verify_successful_otp_verification!(cmd)
       end
     end
 
-    context 'when API error occurs' do
-      let(:cmd) { "#{gitlab_shell_path} key-101" }
+    it 'shows an error when an invalid otp is provided' do
+      Open3.popen2(env, cmd) do |stdin, stdout|
+        asks_for_otp(stdout)
+        stdin.puts('000000')
 
-      it 'prints the error message' do
-        Open3.popen2(env, cmd) do |stdin, stdout|
-          expect(stdout.gets(5)).to eq('OTP: ')
-
-          stdin.puts('123456')
-
-          expect(stdout.flush.read).to eq("\nOTP validation failed.\nboom!\n")
-        end
+        expect(stdout.flush.read).to eq("\nOTP validation failed: boom!\n")
       end
     end
   end
 
-  def verify_successful_verification!(cmd)
+  describe 'authorizing via push' do
+    context 'when key is provided' do
+      let(:cmd) { "#{gitlab_shell_path} key-101" }
+
+      it 'asks a user for a correct OTP' do
+        verify_successful_push_verification!(cmd)
+      end
+    end
+
+    context 'when username is provided' do
+      let(:cmd) { "#{gitlab_shell_path} username-another" }
+
+      it 'asks a user for a correct OTP' do
+        verify_successful_push_verification!(cmd)
+      end
+    end
+  end
+
+  def verify_successful_otp_verification!(cmd)
     Open3.popen2(env, cmd) do |stdin, stdout|
-      expect(stdout.gets(5)).to eq('OTP: ')
-
-      stdin.puts('123456')
+      asks_for_otp(stdout)
+      stdin.puts(correct_otp)
 
       expect(stdout.flush.read).to eq("\nOTP validation successful. Git operations are now allowed.\n")
     end
   end
+
+  def verify_successful_push_verification!(cmd)
+    Open3.popen2(env, cmd) do |stdin, stdout|
+      asks_for_otp(stdout)
+
+      expect(stdout.flush.read).to eq("\nOTP has been validated by Push Authentication. Git operations are now allowed.\n")
+    end
+  end
+
+  def asks_for_otp(stdout)
+    expect(stdout.gets(5)).to eq('OTP: ')
+  end
 end
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1649505502 -14400
#      Sat Apr 09 15:58:22 2022 +0400
# Node ID 7a969fe59508e1e4661d3103d063604f19d585f7
# Parent  7a403c86ddc5c76f20d2f07f247092aa2afe4837
Add developer documentation to command package

diff --git a/internal/command/README.md b/internal/command/README.md
new file mode 100644
--- /dev/null
+++ b/internal/command/README.md
@@ -0,0 +1,26 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Overview
+
+This package consists of a set of packages that are responsible for executing a particular command/feature/operation.
+The full list of features can be viewed [here](https://gitlab.com/gitlab-org/gitlab-shell/-/blob/main/doc/features.md).
+The commands implement the common interface:
+
+```go
+type Command interface {
+	Execute(ctx context.Context) error
+}
+```
+
+A command is executed by running the `Execute` method. The execution logic mostly shares the common pattern:
+
+- Parse the arguments and validate them.
+- Communicate with GitLab Rails using [gitlabnet](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/gitlabnet) package. For example, it can be checking whether a client is authorized to execute this particular command or asking for a personal access token in order to return it to the client.
+- If a command is related to Git operations, establish a connection with Gitaly using [handler](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/handler) and [gitaly](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/gitaly) packages and provide two-way communication between Gitaly and the client.
+- Return results to the client.
+
+[cmd/gitlab-shell/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/gitlab-shell/command) is using this package to build a particular command based on the passed arguments.
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1654254444 0
#      Fri Jun 03 11:07:24 2022 +0000
# Node ID cf0db2e04f7269d3af3a6b5c6cac0a150fa388e9
# Parent  7a969fe59508e1e4661d3103d063604f19d585f7
Specify all packages that use commands

diff --git a/internal/command/README.md b/internal/command/README.md
--- a/internal/command/README.md
+++ b/internal/command/README.md
@@ -23,4 +23,8 @@
 - If a command is related to Git operations, establish a connection with Gitaly using [handler](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/handler) and [gitaly](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/gitaly) packages and provide two-way communication between Gitaly and the client.
 - Return results to the client.
 
-[cmd/gitlab-shell/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/gitlab-shell/command) is using this package to build a particular command based on the passed arguments.
+This package is being used to build a particular command based on the passed arguments in the following files that are under `cmd` directory:
+- [cmd/gitlab-shell/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/gitlab-shell/command)
+- [cmd/check/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/check/command)
+- [cmd/gitlab-shell-authorized-keys-check/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/gitlab-shell-authorized-keys-check/command)
+- [cmd/gitlab-shell-authorized-principals-check/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/gitlab-shell-authorized-principals-check/command)
# HG changeset patch
# User Patrick Bajao <ebajao@gitlab.com>
# Date 1663745718 0
#      Wed Sep 21 07:35:18 2022 +0000
# Node ID 306cbe44b82355a5fa0f7c0cfd01a2d2c7f23ac3
# Parent  2640fced7a7dd0d0a80a54598e441c666a60906d
# Parent  cf0db2e04f7269d3af3a6b5c6cac0a150fa388e9
Merge branch 'id-add-documentation-to-command' into 'main'

Add developer documentation to command package

See merge request https://gitlab.com/gitlab-org/gitlab-shell/-/merge_requests/594

Merged-by: Patrick Bajao <ebajao@gitlab.com>
Approved-by: Patrick Bajao <ebajao@gitlab.com>
Co-authored-by: Igor Drozdov <idrozdov@gitlab.com>

diff --git a/internal/command/README.md b/internal/command/README.md
new file mode 100644
--- /dev/null
+++ b/internal/command/README.md
@@ -0,0 +1,30 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Overview
+
+This package consists of a set of packages that are responsible for executing a particular command/feature/operation.
+The full list of features can be viewed [here](https://gitlab.com/gitlab-org/gitlab-shell/-/blob/main/doc/features.md).
+The commands implement the common interface:
+
+```go
+type Command interface {
+	Execute(ctx context.Context) error
+}
+```
+
+A command is executed by running the `Execute` method. The execution logic mostly shares the common pattern:
+
+- Parse the arguments and validate them.
+- Communicate with GitLab Rails using [gitlabnet](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/gitlabnet) package. For example, it can be checking whether a client is authorized to execute this particular command or asking for a personal access token in order to return it to the client.
+- If a command is related to Git operations, establish a connection with Gitaly using [handler](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/handler) and [gitaly](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/gitaly) packages and provide two-way communication between Gitaly and the client.
+- Return results to the client.
+
+This package is being used to build a particular command based on the passed arguments in the following files that are under `cmd` directory:
+- [cmd/gitlab-shell/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/gitlab-shell/command)
+- [cmd/check/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/check/command)
+- [cmd/gitlab-shell-authorized-keys-check/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/gitlab-shell-authorized-keys-check/command)
+- [cmd/gitlab-shell-authorized-principals-check/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/gitlab-shell-authorized-principals-check/command)
# HG changeset patch
# User Amy Qualls <aqualls@gitlab.com>
# Date 1663840209 0
#      Thu Sep 22 09:50:09 2022 +0000
# Node ID 4389cacccc8dda3ec99f9810ba9a3caec54c0eba
# Parent  306cbe44b82355a5fa0f7c0cfd01a2d2c7f23ac3
Update docs metadata link in gitlab-shell repo

diff --git a/doc/beginners_guide.md b/doc/beginners_guide.md
--- a/doc/beginners_guide.md
+++ b/doc/beginners_guide.md
@@ -1,7 +1,7 @@
 ---
 stage: Create
 group: Source Code
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
 ---
 
 # Beginner's guide to GitLab Shell contributions
diff --git a/doc/features.md b/doc/features.md
--- a/doc/features.md
+++ b/doc/features.md
@@ -1,7 +1,7 @@
 ---
 stage: Create
 group: Source Code
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
 ---
 
 # Feature list
# HG changeset patch
# User Alejandro Rodríguez <alejandro@gitlab.com>
# Date 1663840210 0
#      Thu Sep 22 09:50:10 2022 +0000
# Node ID 519720296fd6a08938196933a308625235332758
# Parent  306cbe44b82355a5fa0f7c0cfd01a2d2c7f23ac3
# Parent  4389cacccc8dda3ec99f9810ba9a3caec54c0eba
Merge branch 'aqualls-update-docs-metadata' into 'main'

Update docs metadata link in gitlab-shell repo

See merge request https://gitlab.com/gitlab-org/gitlab-shell/-/merge_requests/684

Merged-by: Alejandro Rodríguez <alejandro@gitlab.com>
Approved-by: Alejandro Rodríguez <alejandro@gitlab.com>
Co-authored-by: Amy Qualls <aqualls@gitlab.com>

diff --git a/doc/beginners_guide.md b/doc/beginners_guide.md
--- a/doc/beginners_guide.md
+++ b/doc/beginners_guide.md
@@ -1,7 +1,7 @@
 ---
 stage: Create
 group: Source Code
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
 ---
 
 # Beginner's guide to GitLab Shell contributions
diff --git a/doc/features.md b/doc/features.md
--- a/doc/features.md
+++ b/doc/features.md
@@ -1,7 +1,7 @@
 ---
 stage: Create
 group: Source Code
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
 ---
 
 # Feature list
# HG changeset patch
# User Stan Hu <stanhu@gmail.com>
# Date 1664255032 25200
#      Mon Sep 26 22:03:52 2022 -0700
# Node ID 52f1fd79a9efb6dfe56f7d688b966cc5b1d8c1d9
# Parent  519720296fd6a08938196933a308625235332758
Bump .tool-versions to use Go 1.18.6

go 1.17 is no longer receiving security updates. Update to go 1.18 in
preparation for upgrading.

Part of https://gitlab.com/groups/gitlab-org/-/epics/8843

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.9
+golang 1.18.6
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1664265694 0
#      Tue Sep 27 08:01:34 2022 +0000
# Node ID 436213b02160b1488ee48fba208bfffcf1532726
# Parent  519720296fd6a08938196933a308625235332758
# Parent  52f1fd79a9efb6dfe56f7d688b966cc5b1d8c1d9
Merge branch 'sh-bump-go-1.18' into 'main'

Bump .tool-versions to use Go 1.18.6

See merge request https://gitlab.com/gitlab-org/gitlab-shell/-/merge_requests/685

Merged-by: Igor Drozdov <idrozdov@gitlab.com>
Approved-by: Igor Drozdov <idrozdov@gitlab.com>
Co-authored-by: Stan Hu <stanhu@gmail.com>

diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.9
+golang 1.18.6
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1664270337 -7200
#      Tue Sep 27 11:18:57 2022 +0200
# Node ID fc481f87acc38d85940b88ca5a3664635f7016d0
# Parent  2640fced7a7dd0d0a80a54598e441c666a60906d
Trim secret before signing JWT tokens

With this change we don't rely on the secret to either contain
a newline or not contain it.

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -20,7 +20,7 @@
 )
 
 var (
-	secret = []byte("sssh, it's a secret")
+	secret = "sssh, it's a secret"
 )
 
 func TestClients(t *testing.T) {
@@ -31,24 +31,29 @@
 		relativeURLRoot string
 		caFile          string
 		server          func(*testing.T, []testserver.TestRequestHandler) string
+		secret          string
 	}{
 		{
 			desc:   "Socket client",
 			server: testserver.StartSocketHttpServer,
+			secret: secret,
 		},
 		{
 			desc:            "Socket client with a relative URL at /",
 			relativeURLRoot: "/",
 			server:          testserver.StartSocketHttpServer,
+			secret:          secret,
 		},
 		{
 			desc:            "Socket client with relative URL at /gitlab",
 			relativeURLRoot: "/gitlab",
 			server:          testserver.StartSocketHttpServer,
+			secret:          secret,
 		},
 		{
 			desc:   "Http client",
 			server: testserver.StartHttpServer,
+			secret: secret,
 		},
 		{
 			desc:   "Https client",
@@ -56,6 +61,15 @@
 			server: func(t *testing.T, handlers []testserver.TestRequestHandler) string {
 				return testserver.StartHttpsServer(t, handlers, "")
 			},
+			secret: secret,
+		},
+		{
+			desc:   "Secret with newlines",
+			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
+			server: func(t *testing.T, handlers []testserver.TestRequestHandler) string {
+				return testserver.StartHttpsServer(t, handlers, "")
+			},
+			secret: "\n" + secret + "\n",
 		},
 	}
 
@@ -66,7 +80,7 @@
 			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", 1, nil)
 			require.NoError(t, err)
 
-			client, err := NewGitlabNetClient("", "", string(secret), httpClient)
+			client, err := NewGitlabNetClient("", "", tc.secret, httpClient)
 			require.NoError(t, err)
 
 			testBrokenRequest(t, client)
@@ -74,7 +88,7 @@
 			testSuccessfulPost(t, client)
 			testMissing(t, client)
 			testErrorMessage(t, client)
-			testAuthenticationHeader(t, client)
+			testAuthenticationHeader(t, tc.secret, client)
 			testJWTAuthenticationHeader(t, client)
 			testXForwardedForHeader(t, client)
 			testHostWithTrailingSlash(t, client)
@@ -154,7 +168,7 @@
 	})
 }
 
-func testAuthenticationHeader(t *testing.T, client *GitlabNetClient) {
+func testAuthenticationHeader(t *testing.T, secret string, client *GitlabNetClient) {
 	t.Run("Authentication headers for GET", func(t *testing.T) {
 		response, err := client.Get(context.Background(), "/auth")
 		require.NoError(t, err)
@@ -167,7 +181,7 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, secret, header)
+		require.Equal(t, secret, string(header))
 	})
 
 	t.Run("Authentication headers for POST", func(t *testing.T) {
@@ -182,7 +196,7 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, secret, header)
+		require.Equal(t, secret, string(header))
 	})
 }
 
@@ -193,7 +207,7 @@
 
 		claims := &jwt.RegisteredClaims{}
 		token, err := jwt.ParseWithClaims(string(responseBody), claims, func(token *jwt.Token) (interface{}, error) {
-			return secret, nil
+			return []byte(secret), nil
 		})
 		require.NoError(t, err)
 		require.True(t, token.Valid)
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -141,9 +141,7 @@
 	if user != "" && password != "" {
 		request.SetBasicAuth(user, password)
 	}
-	secretBytes := []byte(c.secret)
-
-	encodedSecret := base64.StdEncoding.EncodeToString(secretBytes)
+	encodedSecret := base64.StdEncoding.EncodeToString([]byte(c.secret))
 	request.Header.Set(secretHeaderName, encodedSecret)
 
 	claims := jwt.RegisteredClaims{
@@ -151,6 +149,7 @@
 		IssuedAt:  jwt.NewNumericDate(time.Now()),
 		ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtTTL)),
 	}
+	secretBytes := []byte(strings.TrimSpace(c.secret))
 	tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secretBytes)
 	if err != nil {
 		return nil, err
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1664352839 0
#      Wed Sep 28 08:13:59 2022 +0000
# Node ID 6f3c156c319912e41280b8778890b0e2b300db14
# Parent  436213b02160b1488ee48fba208bfffcf1532726
# Parent  fc481f87acc38d85940b88ca5a3664635f7016d0
Merge branch 'id-fix-jwt-tokens' into 'main'

Trim secret before signing JWT tokens

See merge request https://gitlab.com/gitlab-org/gitlab-shell/-/merge_requests/686

Merged-by: Ash McKenzie <amckenzie@gitlab.com>
Approved-by: Alejandro Rodríguez <alejandro@gitlab.com>
Approved-by: Ash McKenzie <amckenzie@gitlab.com>
Co-authored-by: Igor Drozdov <idrozdov@gitlab.com>

diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -20,7 +20,7 @@
 )
 
 var (
-	secret = []byte("sssh, it's a secret")
+	secret = "sssh, it's a secret"
 )
 
 func TestClients(t *testing.T) {
@@ -31,24 +31,29 @@
 		relativeURLRoot string
 		caFile          string
 		server          func(*testing.T, []testserver.TestRequestHandler) string
+		secret          string
 	}{
 		{
 			desc:   "Socket client",
 			server: testserver.StartSocketHttpServer,
+			secret: secret,
 		},
 		{
 			desc:            "Socket client with a relative URL at /",
 			relativeURLRoot: "/",
 			server:          testserver.StartSocketHttpServer,
+			secret:          secret,
 		},
 		{
 			desc:            "Socket client with relative URL at /gitlab",
 			relativeURLRoot: "/gitlab",
 			server:          testserver.StartSocketHttpServer,
+			secret:          secret,
 		},
 		{
 			desc:   "Http client",
 			server: testserver.StartHttpServer,
+			secret: secret,
 		},
 		{
 			desc:   "Https client",
@@ -56,6 +61,15 @@
 			server: func(t *testing.T, handlers []testserver.TestRequestHandler) string {
 				return testserver.StartHttpsServer(t, handlers, "")
 			},
+			secret: secret,
+		},
+		{
+			desc:   "Secret with newlines",
+			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
+			server: func(t *testing.T, handlers []testserver.TestRequestHandler) string {
+				return testserver.StartHttpsServer(t, handlers, "")
+			},
+			secret: "\n" + secret + "\n",
 		},
 	}
 
@@ -66,7 +80,7 @@
 			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", 1, nil)
 			require.NoError(t, err)
 
-			client, err := NewGitlabNetClient("", "", string(secret), httpClient)
+			client, err := NewGitlabNetClient("", "", tc.secret, httpClient)
 			require.NoError(t, err)
 
 			testBrokenRequest(t, client)
@@ -74,7 +88,7 @@
 			testSuccessfulPost(t, client)
 			testMissing(t, client)
 			testErrorMessage(t, client)
-			testAuthenticationHeader(t, client)
+			testAuthenticationHeader(t, tc.secret, client)
 			testJWTAuthenticationHeader(t, client)
 			testXForwardedForHeader(t, client)
 			testHostWithTrailingSlash(t, client)
@@ -154,7 +168,7 @@
 	})
 }
 
-func testAuthenticationHeader(t *testing.T, client *GitlabNetClient) {
+func testAuthenticationHeader(t *testing.T, secret string, client *GitlabNetClient) {
 	t.Run("Authentication headers for GET", func(t *testing.T) {
 		response, err := client.Get(context.Background(), "/auth")
 		require.NoError(t, err)
@@ -167,7 +181,7 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, secret, header)
+		require.Equal(t, secret, string(header))
 	})
 
 	t.Run("Authentication headers for POST", func(t *testing.T) {
@@ -182,7 +196,7 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, secret, header)
+		require.Equal(t, secret, string(header))
 	})
 }
 
@@ -193,7 +207,7 @@
 
 		claims := &jwt.RegisteredClaims{}
 		token, err := jwt.ParseWithClaims(string(responseBody), claims, func(token *jwt.Token) (interface{}, error) {
-			return secret, nil
+			return []byte(secret), nil
 		})
 		require.NoError(t, err)
 		require.True(t, token.Valid)
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -141,9 +141,7 @@
 	if user != "" && password != "" {
 		request.SetBasicAuth(user, password)
 	}
-	secretBytes := []byte(c.secret)
-
-	encodedSecret := base64.StdEncoding.EncodeToString(secretBytes)
+	encodedSecret := base64.StdEncoding.EncodeToString([]byte(c.secret))
 	request.Header.Set(secretHeaderName, encodedSecret)
 
 	claims := jwt.RegisteredClaims{
@@ -151,6 +149,7 @@
 		IssuedAt:  jwt.NewNumericDate(time.Now()),
 		ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtTTL)),
 	}
+	secretBytes := []byte(strings.TrimSpace(c.secret))
 	tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secretBytes)
 	if err != nil {
 		return nil, err
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1664355316 -7200
#      Wed Sep 28 10:55:16 2022 +0200
# Node ID 7349d3cd7ec70f9378771a4696f3b19c98b69285
# Parent  6f3c156c319912e41280b8778890b0e2b300db14
Release v14.12.0

- Trim secret before signing JWT tokens !686
- Bump .tool-versions to use Go 1.18.6 !685
- Update Gitaly to 15.4.0-rc2 !681
- Test against Golang v1.19 !680

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,10 @@
+v14.12.0
+
+- Trim secret before signing JWT tokens !686
+- Bump .tool-versions to use Go 1.18.6 !685
+- Update Gitaly to 15.4.0-rc2 !681
+- Test against Golang v1.19 !680
+
 v14.11.0
 
 - Update Gitaly to v15 !676
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.11.0
+14.12.0
# HG changeset patch
# User Igor Drozdov <idrozdov@gitlab.com>
# Date 1664355922 0
#      Wed Sep 28 09:05:22 2022 +0000
# Node ID 2c02c21df3911c7d4cf2bb425be879e2786c2b69
# Parent  6f3c156c319912e41280b8778890b0e2b300db14
# Parent  7349d3cd7ec70f9378771a4696f3b19c98b69285
Merge branch 'id-release-14-12-0' into 'main'

Release v14.12.0

See merge request https://gitlab.com/gitlab-org/gitlab-shell/-/merge_requests/687

Merged-by: Igor Drozdov <idrozdov@gitlab.com>
Approved-by: Igor Drozdov <idrozdov@gitlab.com>

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,10 @@
+v14.12.0
+
+- Trim secret before signing JWT tokens !686
+- Bump .tool-versions to use Go 1.18.6 !685
+- Update Gitaly to 15.4.0-rc2 !681
+- Test against Golang v1.19 !680
+
 v14.11.0
 
 - Update Gitaly to v15 !676
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.11.0
+14.12.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1681211144 -7200
#      Tue Apr 11 13:05:44 2023 +0200
# Branch heptapod
# Node ID 8ecde27776526bb72c31795c54d68ac62e3b8a9d
# Parent  61e96ffd4b49d8579dfda6a058fb4b62e7362183
# Parent  2c02c21df3911c7d4cf2bb425be879e2786c2b69
Merged upstream v14.12.0 into Heptapod Shell

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -61,7 +61,7 @@
   image: golang:${GO_VERSION}
   parallel:
     matrix:
-      - GO_VERSION: ["1.17", "1.18"]
+      - GO_VERSION: ["1.17", "1.18", "1.19"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.9
+golang 1.18.6
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,15 @@
+v14.12.0
+
+- Trim secret before signing JWT tokens !686
+- Bump .tool-versions to use Go 1.18.6 !685
+- Update Gitaly to 15.4.0-rc2 !681
+- Test against Golang v1.19 !680
+
+v14.11.0
+
+- Update Gitaly to v15 !676
+- Fixed extra slashes in API request paths generated for geo !673
+
 v14.10.0
 
 - Implement Push Auth support for 2FA verification !454
diff --git a/Dangerfile b/Dangerfile
--- a/Dangerfile
+++ b/Dangerfile
@@ -5,5 +5,5 @@
 Gitlab::Dangerfiles.for_project(self) do |gitlab_dangerfiles|
   gitlab_dangerfiles.import_plugins
 
-  gitlab_dangerfiles.import_dangerfiles(except: %w[changelog commit_messages simple_roulette])
+  gitlab_dangerfiles.import_dangerfiles(except: %w[changelog commit_messages])
 end
diff --git a/Gemfile b/Gemfile
--- a/Gemfile
+++ b/Gemfile
@@ -5,5 +5,5 @@
 end
 
 group :development, :danger do
-  gem 'gitlab-dangerfiles', '~> 3.0.0'
+  gem 'gitlab-dangerfiles', '~> 3.5.1'
 end
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -11,7 +11,7 @@
     colored2 (3.1.2)
     cork (0.3.0)
       colored2 (~> 3.1)
-    danger (8.5.0)
+    danger (8.6.1)
       claide (~> 1.0)
       claide-plugins (>= 0.9.2)
       colored2 (~> 3.1)
@@ -28,7 +28,7 @@
       danger
       gitlab (~> 4.2, >= 4.2.0)
     diff-lcs (1.3)
-    faraday (1.10.0)
+    faraday (1.10.1)
       faraday-em_http (~> 1.0)
       faraday-em_synchrony (~> 1.0)
       faraday-excon (~> 1.1)
@@ -43,29 +43,29 @@
     faraday-em_http (1.0.0)
     faraday-em_synchrony (1.0.0)
     faraday-excon (1.1.0)
-    faraday-http-cache (2.2.0)
+    faraday-http-cache (2.4.1)
       faraday (>= 0.8)
     faraday-httpclient (1.0.1)
-    faraday-multipart (1.0.3)
-      multipart-post (>= 1.2, < 3)
+    faraday-multipart (1.0.4)
+      multipart-post (~> 2)
     faraday-net_http (1.0.1)
     faraday-net_http_persistent (1.2.0)
     faraday-patron (1.0.0)
     faraday-rack (1.0.0)
     faraday-retry (1.0.3)
-    git (1.10.2)
+    git (1.11.0)
       rchardet (~> 1.8)
-    gitlab (4.18.0)
-      httparty (~> 0.18)
+    gitlab (4.19.0)
+      httparty (~> 0.20)
       terminal-table (>= 1.5.1)
-    gitlab-dangerfiles (3.0.0)
+    gitlab-dangerfiles (3.5.1)
       danger (>= 8.4.5)
       danger-gitlab (>= 8.0.0)
       rake
     httparty (0.20.0)
       mime-types (~> 3.0)
       multi_xml (>= 0.5.2)
-    kramdown (2.3.2)
+    kramdown (2.4.0)
       rexml
     kramdown-parser-gfm (1.1.0)
       kramdown (~> 2.0)
@@ -73,14 +73,14 @@
       mime-types-data (~> 3.2015)
     mime-types-data (3.2022.0105)
     multi_xml (0.6.0)
-    multipart-post (2.1.1)
+    multipart-post (2.2.3)
     nap (1.1.0)
     no_proxy_fix (0.1.2)
-    octokit (4.22.0)
-      faraday (>= 0.9)
-      sawyer (~> 0.8.0, >= 0.5.3)
+    octokit (4.25.1)
+      faraday (>= 1, < 3)
+      sawyer (~> 0.9)
     open4 (1.3.4)
-    public_suffix (4.0.6)
+    public_suffix (4.0.7)
     rake (13.0.6)
     rchardet (1.8.0)
     rexml (3.2.5)
@@ -98,18 +98,18 @@
       rspec-support (~> 3.8.0)
     rspec-support (3.8.0)
     ruby2_keywords (0.0.5)
-    sawyer (0.8.2)
+    sawyer (0.9.2)
       addressable (>= 2.3.5)
-      faraday (> 0.8, < 2.0)
+      faraday (>= 0.17.3, < 3)
     terminal-table (3.0.2)
       unicode-display_width (>= 1.1.1, < 3)
-    unicode-display_width (2.1.0)
+    unicode-display_width (2.2.0)
 
 PLATFORMS
   ruby
 
 DEPENDENCIES
-  gitlab-dangerfiles (~> 3.0.0)
+  gitlab-dangerfiles (~> 3.5.1)
   rspec (~> 3.8.0)
 
 BUNDLED WITH
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 14.12.0
+
+- Bump to upstream GitLab Shell v14.12.0 (GitLab 15.5)
+
 ## 14.10.0
 
 - Bump to upstream GitLab Shell v14.10.0 (GitLab 15.3 and 15.4 series)
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-14.10.0
+14.12.0
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.10.0
+14.12.0
diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -20,7 +20,7 @@
 )
 
 var (
-	secret = []byte("sssh, it's a secret")
+	secret = "sssh, it's a secret"
 )
 
 func TestClients(t *testing.T) {
@@ -31,24 +31,29 @@
 		relativeURLRoot string
 		caFile          string
 		server          func(*testing.T, []testserver.TestRequestHandler) string
+		secret          string
 	}{
 		{
 			desc:   "Socket client",
 			server: testserver.StartSocketHttpServer,
+			secret: secret,
 		},
 		{
 			desc:            "Socket client with a relative URL at /",
 			relativeURLRoot: "/",
 			server:          testserver.StartSocketHttpServer,
+			secret:          secret,
 		},
 		{
 			desc:            "Socket client with relative URL at /gitlab",
 			relativeURLRoot: "/gitlab",
 			server:          testserver.StartSocketHttpServer,
+			secret:          secret,
 		},
 		{
 			desc:   "Http client",
 			server: testserver.StartHttpServer,
+			secret: secret,
 		},
 		{
 			desc:   "Https client",
@@ -56,6 +61,15 @@
 			server: func(t *testing.T, handlers []testserver.TestRequestHandler) string {
 				return testserver.StartHttpsServer(t, handlers, "")
 			},
+			secret: secret,
+		},
+		{
+			desc:   "Secret with newlines",
+			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
+			server: func(t *testing.T, handlers []testserver.TestRequestHandler) string {
+				return testserver.StartHttpsServer(t, handlers, "")
+			},
+			secret: "\n" + secret + "\n",
 		},
 	}
 
@@ -66,7 +80,7 @@
 			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", 1, nil)
 			require.NoError(t, err)
 
-			client, err := NewGitlabNetClient("", "", string(secret), httpClient)
+			client, err := NewGitlabNetClient("", "", tc.secret, httpClient)
 			require.NoError(t, err)
 
 			testBrokenRequest(t, client)
@@ -74,9 +88,10 @@
 			testSuccessfulPost(t, client)
 			testMissing(t, client)
 			testErrorMessage(t, client)
-			testAuthenticationHeader(t, client)
+			testAuthenticationHeader(t, tc.secret, client)
 			testJWTAuthenticationHeader(t, client)
 			testXForwardedForHeader(t, client)
+			testHostWithTrailingSlash(t, client)
 		})
 	}
 }
@@ -153,7 +168,7 @@
 	})
 }
 
-func testAuthenticationHeader(t *testing.T, client *GitlabNetClient) {
+func testAuthenticationHeader(t *testing.T, secret string, client *GitlabNetClient) {
 	t.Run("Authentication headers for GET", func(t *testing.T) {
 		response, err := client.Get(context.Background(), "/auth")
 		require.NoError(t, err)
@@ -166,7 +181,7 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, secret, header)
+		require.Equal(t, secret, string(header))
 	})
 
 	t.Run("Authentication headers for POST", func(t *testing.T) {
@@ -181,7 +196,7 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, secret, header)
+		require.Equal(t, secret, string(header))
 	})
 }
 
@@ -192,7 +207,7 @@
 
 		claims := &jwt.RegisteredClaims{}
 		token, err := jwt.ParseWithClaims(string(responseBody), claims, func(token *jwt.Token) (interface{}, error) {
-			return secret, nil
+			return []byte(secret), nil
 		})
 		require.NoError(t, err)
 		require.True(t, token.Valid)
@@ -237,6 +252,16 @@
 	})
 }
 
+func testHostWithTrailingSlash(t *testing.T, client *GitlabNetClient) {
+	oldHost := client.httpClient.Host
+	client.httpClient.Host = oldHost + "/"
+
+	testSuccessfulGet(t, client)
+	testSuccessfulPost(t, client)
+
+	client.httpClient.Host = oldHost
+}
+
 func buildRequests(t *testing.T, relativeURLRoot string) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -85,6 +85,10 @@
 	return path
 }
 
+func appendPath(host string, path string) string {
+	return strings.TrimSuffix(host, "/") + "/" + strings.TrimPrefix(path, "/")
+}
+
 func newRequest(ctx context.Context, method, host, path string, data interface{}) (*http.Request, error) {
 	var jsonReader io.Reader
 	if data != nil {
@@ -96,7 +100,7 @@
 		jsonReader = bytes.NewReader(jsonData)
 	}
 
-	request, err := http.NewRequestWithContext(ctx, method, host+path, jsonReader)
+	request, err := http.NewRequestWithContext(ctx, method, appendPath(host, path), jsonReader)
 	if err != nil {
 		return nil, err
 	}
@@ -137,9 +141,7 @@
 	if user != "" && password != "" {
 		request.SetBasicAuth(user, password)
 	}
-	secretBytes := []byte(c.secret)
-
-	encodedSecret := base64.StdEncoding.EncodeToString(secretBytes)
+	encodedSecret := base64.StdEncoding.EncodeToString([]byte(c.secret))
 	request.Header.Set(secretHeaderName, encodedSecret)
 
 	claims := jwt.RegisteredClaims{
@@ -147,6 +149,7 @@
 		IssuedAt:  jwt.NewNumericDate(time.Now()),
 		ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtTTL)),
 	}
+	secretBytes := []byte(strings.TrimSpace(c.secret))
 	tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secretBytes)
 	if err != nil {
 		return nil, err
diff --git a/client/testserver/gitalyserver.go b/client/testserver/gitalyserver.go
--- a/client/testserver/gitalyserver.go
+++ b/client/testserver/gitalyserver.go
@@ -10,8 +10,8 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/labkit/log"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials/insecure"
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -22,8 +22,8 @@
 	"github.com/mikesmitty/edkey"
 	"github.com/pires/go-proxyproto"
 	"github.com/stretchr/testify/require"
-	gitalyClient "gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	gitalyClient "gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 	"golang.org/x/crypto/ssh"
 )
@@ -387,12 +387,25 @@
 	ensureGitalyRepository(t)
 
 	client := runSSHD(t, successAPI(t))
-
 	session, err := client.NewSession()
 	require.NoError(t, err)
 	defer session.Close()
 
-	output, err := session.Output(fmt.Sprintf("git-receive-pack %s", testRepo))
+	stdin, err := session.StdinPipe()
+	require.NoError(t, err)
+
+	stdout, err := session.StdoutPipe()
+	require.NoError(t, err)
+
+	err = session.Start(fmt.Sprintf("git-receive-pack %s", testRepo))
+	require.NoError(t, err)
+
+	// Gracefully close connection
+	_, err = fmt.Fprintln(stdin, "0000")
+	require.NoError(t, err)
+	stdin.Close()
+
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 
 	outputLines := strings.Split(string(output), "\n")
diff --git a/doc/beginners_guide.md b/doc/beginners_guide.md
--- a/doc/beginners_guide.md
+++ b/doc/beginners_guide.md
@@ -1,7 +1,7 @@
 ---
 stage: Create
 group: Source Code
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
 ---
 
 # Beginner's guide to GitLab Shell contributions
diff --git a/doc/features.md b/doc/features.md
--- a/doc/features.md
+++ b/doc/features.md
@@ -1,7 +1,7 @@
 ---
 stage: Create
 group: Source Code
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
 ---
 
 # Feature list
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -10,27 +10,29 @@
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.2
-	github.com/prometheus/client_golang v1.12.1
-	github.com/sirupsen/logrus v1.8.1
-	github.com/stretchr/testify v1.7.0
-	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
+	github.com/prometheus/client_golang v1.12.2
+	github.com/sirupsen/logrus v1.9.0
+	github.com/stretchr/testify v1.8.0
+	gitlab.com/gitlab-org/gitaly/v15 v15.4.0-rc2
 	gitlab.com/gitlab-org/labkit v1.16.0
-	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
-	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	google.golang.org/grpc v1.40.0
+	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
+	golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
+	google.golang.org/grpc v1.48.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
 require (
-	cloud.google.com/go v0.92.2 // indirect
+	cloud.google.com/go v0.100.2 // indirect
+	cloud.google.com/go/compute v1.5.0 // indirect
+	cloud.google.com/go/monitoring v1.4.0 // indirect
 	cloud.google.com/go/profiler v0.1.0 // indirect
-	cloud.google.com/go/trace v0.1.0 // indirect
-	contrib.go.opencensus.io/exporter/stackdriver v0.13.8 // indirect
+	cloud.google.com/go/trace v1.2.0 // indirect
+	contrib.go.opencensus.io/exporter/stackdriver v0.13.10 // indirect
 	github.com/DataDog/datadog-go v4.4.0+incompatible // indirect
 	github.com/DataDog/sketches-go v1.0.0 // indirect
 	github.com/Microsoft/go-winio v0.5.0 // indirect
 	github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
-	github.com/aws/aws-sdk-go v1.38.35 // indirect
+	github.com/aws/aws-sdk-go v1.43.31 // indirect
 	github.com/beevik/ntp v0.3.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
@@ -41,12 +43,13 @@
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
-	github.com/google/go-cmp v0.5.6 // indirect
+	github.com/google/go-cmp v0.5.8 // indirect
 	github.com/google/pprof v0.0.0-20210804190019-f964ff605595 // indirect
-	github.com/google/uuid v1.2.0 // indirect
-	github.com/googleapis/gax-go/v2 v2.0.5 // indirect
-	github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 // indirect
+	github.com/google/uuid v1.3.0 // indirect
+	github.com/googleapis/gax-go/v2 v2.2.0 // indirect
+	github.com/hashicorp/yamux v0.1.1 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	github.com/kr/text v0.2.0 // indirect
 	github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 // indirect
 	github.com/lightstep/lightstep-tracer-go v0.25.0 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
@@ -63,22 +66,23 @@
 	github.com/tinylib/msgp v1.1.2 // indirect
 	github.com/tklauser/go-sysconf v0.3.4 // indirect
 	github.com/tklauser/numcpus v0.2.1 // indirect
-	github.com/uber/jaeger-client-go v2.29.1+incompatible // indirect
+	github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
 	github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
 	go.opencensus.io v0.23.0 // indirect
-	go.uber.org/atomic v1.7.0 // indirect
-	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
-	golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a // indirect
-	golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
+	go.uber.org/atomic v1.9.0 // indirect
+	golang.org/x/net v0.0.0-20220531201128-c960675eff93 // indirect
+	golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
+	golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
 	golang.org/x/text v0.3.7 // indirect
-	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
+	golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
-	google.golang.org/api v0.54.0 // indirect
+	google.golang.org/api v0.74.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20210813162853-db860fec028c // indirect
-	google.golang.org/protobuf v1.27.1 // indirect
+	google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de // indirect
+	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
+	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
 replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -1,9 +1,6 @@
-bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
-bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
 cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
@@ -22,104 +19,123 @@
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
 cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.82.0/go.mod h1:vlKccHJGuFBFufnAnuB08dfEH9Y3H7dzDzRECFdC2TA=
 cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
 cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
 cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
 cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
-cloud.google.com/go v0.92.2 h1:podK44+0gcW5rWGMjJiPH0+rzkCTQx/zT0qF5CLqVkM=
 cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
+cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
+cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
+cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U=
+cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y=
+cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
+cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw=
+cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
+cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM=
+cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
+cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=
+cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw=
+cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc=
+cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
+cloud.google.com/go/kms v1.1.0/go.mod h1:WdbppnCDMDpOvoYBMn1+gNmOeEoZYqAv+HeuKARGCXI=
+cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA=
+cloud.google.com/go/monitoring v1.1.0/go.mod h1:L81pzz7HKn14QCMaCs6NTQkdBnE87TElyanS95vIcl4=
+cloud.google.com/go/monitoring v1.4.0 h1:05+IuNMbh40hbxcqQ4SnynbwZbLG1Wc9dysIJxnfv7U=
+cloud.google.com/go/monitoring v1.4.0/go.mod h1:y6xnxfwI3hTFWOdkOaD7nfJVlwuC3/mS/5kvtT131p4=
 cloud.google.com/go/profiler v0.1.0 h1:MG/rxKC1MztRfEWMGYKFISxyZak5hNh29f0A/z2tvWk=
 cloud.google.com/go/profiler v0.1.0/go.mod h1:D7S7LV/zKbRWkOzYL1b5xytpqt8Ikd/v/yvf1/Tx2pQ=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
 cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
-cloud.google.com/go/pubsub v1.10.3/go.mod h1:FUcc28GpGxxACoklPsE1sCtbkY4Ix+ro7yvw+h82Jn4=
+cloud.google.com/go/pubsub v1.19.0/go.mod h1:/O9kmSe9bb9KRnIAWkzmqhPjHo6LtzGOBYd/kr06XSs=
+cloud.google.com/go/secretmanager v1.3.0/go.mod h1:+oLTkouyiYiabAQNugCeTS3PAArGiMJuBqvJnJsyH+U=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-cloud.google.com/go/storage v1.15.0 h1:Ljj+ZXVEhCr/1+4ZhvtteN1ND7UUsNTlduGclLh8GO0=
-cloud.google.com/go/storage v1.15.0/go.mod h1:mjjQMoxxyGH7Jr8K5qrx6N2O0AHsczI61sMNn03GIZI=
-cloud.google.com/go/trace v0.1.0 h1:nUGUK79FOkN0UGUXhBmVBkbu1PYsHe0YyFSPLOD9Npg=
+cloud.google.com/go/storage v1.21.0 h1:HwnT2u2D309SFDHQII6m18HlrCi3jAXhUMTLOWXYH14=
+cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKuqnZI01LAA=
 cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g=
+cloud.google.com/go/trace v1.0.0/go.mod h1:4iErSByzxkyHWzzlAj63/Gmjz0NH1ASqhJguHpGcr6A=
+cloud.google.com/go/trace v1.2.0 h1:oIaB4KahkIUOpLSAAjEJ8y2desbjY/x/RfP4O3KAtTI=
+cloud.google.com/go/trace v1.2.0/go.mod h1:Wc8y/uYyOhPy12KEnXG9XGrvfMz5F5SrYecQlbW1rwM=
 contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
-contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
-contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.10 h1:a9+GZPUe+ONKUwULjlEOucMMG0qfSCCenlji0Nhqbys=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.10/go.mod h1:I5htMbyta491eUxufwwZPQdcKvvgzMB4O9ni41YnIM8=
 contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
-github.com/Azure/azure-amqp-common-go/v3 v3.1.0/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0=
+github.com/Azure/azure-amqp-common-go/v3 v3.2.1/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI=
+github.com/Azure/azure-amqp-common-go/v3 v3.2.2/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI=
 github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
 github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
-github.com/Azure/azure-sdk-for-go v54.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
-github.com/Azure/azure-service-bus-go v0.10.11/go.mod h1:AWw9eTTWZVZyvgpPahD1ybz3a8/vT3GsJDS8KYex55U=
-github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nUdBD+e64lEuc4sVnuOfNs=
-github.com/Azure/go-amqp v0.13.0/go.mod h1:qj+o8xPCz9tMSbQ83Vp8boHahuRDl5mkNHyt1xlxUTs=
-github.com/Azure/go-amqp v0.13.4/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
-github.com/Azure/go-amqp v0.13.7/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
+github.com/Azure/azure-sdk-for-go v59.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
+github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
+github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU=
+github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck=
+github.com/Azure/go-amqp v0.16.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
+github.com/Azure/go-amqp v0.16.4/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
 github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
-github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
 github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
-github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
-github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE=
+github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
+github.com/Azure/go-autorest/autorest v0.11.22/go.mod h1:BAWYUWGPEtKPzjVkp0Q6an0MJcJDsoh5Z1BFAEFs4Xs=
 github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
-github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
 github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
-github.com/Azure/go-autorest/autorest/azure/auth v0.5.7/go.mod h1:AkzUsqkrdmNhfP2i54HqINVQopw0CLDnvHpJ88Zz1eI=
+github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
+github.com/Azure/go-autorest/autorest/adal v0.9.17/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.9/go.mod h1:hg3/1yw0Bq87O3KvvnJoAh34/0zbP7SFizX/qN5JvjU=
 github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
 github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
-github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
 github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
 github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
 github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
-github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
 github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
-github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w=
 github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
 github.com/DataDog/datadog-go v4.4.0+incompatible h1:R7WqXWP4fIOAqWJtUKmSfuc7eDsBT58k9AY5WSHVosk=
 github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
 github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
 github.com/DataDog/sketches-go v1.0.0 h1:chm5KSXO7kO+ywGWJ0Zs6tdmWU8PBXSbywFVciL6BG4=
 github.com/DataDog/sketches-go v1.0.0/go.mod h1:O+XkJHWk9w4hDwY2ZUDU31ZC9sNYlYo8DiFsxjYeo1k=
-github.com/GoogleCloudPlatform/cloudsql-proxy v1.22.0/go.mod h1:mAm5O/zik2RFmcpigNjg6nMotDL8ZXJaxKzgGVcSMFA=
-github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
+github.com/GoogleCloudPlatform/cloudsql-proxy v1.29.0/go.mod h1:spvB9eLJH9dutlbPSRmHvSXXHOwGRyeXh1jVdquA2G8=
 github.com/HdrHistogram/hdrhistogram-go v1.1.1 h1:cJXY5VLMHgejurPjZH6Fo9rIwRGLefBGdiaENZALqrg=
 github.com/HdrHistogram/hdrhistogram-go v1.1.1/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
-github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
-github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
-github.com/Microsoft/go-winio v0.4.19/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
 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/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
 github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
-github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
-github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 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=
-github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
-github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
 github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
@@ -128,40 +144,56 @@
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
-github.com/alexbrainman/sspi v0.0.0-20180125232955-4729b3d4d858/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
+github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
-github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
-github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
-github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
 github.com/avast/retry-go v2.4.2+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
-github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
+github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
-github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
-github.com/aws/aws-sdk-go v1.38.35 h1:7AlAO0FC+8nFjxiGKEmq0QLpiA8/XFr6eIxgRTwkdTg=
-github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
-github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
+github.com/aws/aws-sdk-go v1.43.31 h1:yJZIr8nMV1hXjAvvOLUFqZRJcHV7udPQBfhJqawDzI0=
+github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
+github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM=
+github.com/aws/aws-sdk-go-v2/config v1.15.3/go.mod h1:9YL3v07Xc/ohTsxFXzan9ZpFpdTOFl4X65BAKYaz8jg=
+github.com/aws/aws-sdk-go-v2/credentials v1.11.2/go.mod h1:j8YsY9TXTm31k4eFhspiQicfXPLZ0gYXA50i4gxPE8g=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3/go.mod h1:uk1vhHHERfSVCUnqSqz8O48LBYDSC+k6brng09jcMOk=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3/go.mod h1:0dHuD2HZZSiwfJSy1FO5bX1hQ1TxVV1QXXjpn3XUE44=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9/go.mod h1:AnVH5pvai0pAF4lXRq0bmhbes1u9R8wTE+g+183bZNM=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10/go.mod h1:8DcYQcz0+ZJaSxANlHIsbbi6S+zMwjwdDqwW3r9AzaE=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3/go.mod h1:Seb8KNmD6kVTjwRjVEgOT5hPin6sq+v4C2ycJQDwuH8=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3/go.mod h1:wlY6SVjuwvh3TVRpTqdy4I1JpBFLX4UGeKZdWntaocw=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3/go.mod h1:Bm/v2IaN6rZ+Op7zX+bOUMdL4fsrYZiD0dsjLhNKwZc=
+github.com/aws/aws-sdk-go-v2/service/kms v1.16.3/go.mod h1:QuiHPBqlOFCi4LqdSskYYAWpQlx3PKmohy+rE2F+o5g=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z4KJ7DtFFCx8fJD2a491Ak=
+github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4/go.mod h1:PJc8s+lxyU8rrre0/4a0pn2wgwiDvOEzoOjcJUBr67o=
+github.com/aws/aws-sdk-go-v2/service/sns v1.17.4/go.mod h1:kElt+uCcXxcqFyc+bQqZPFD9DME/eC6oHBXvFzQ9Bcw=
+github.com/aws/aws-sdk-go-v2/service/sqs v1.18.3/go.mod h1:skmQo0UPvsjsuYYSYMVmrPc1HWCbHUJyrCEp+ZaLzqM=
+github.com/aws/aws-sdk-go-v2/service/ssm v1.24.1/go.mod h1:NR/xoKjdbRJ+qx0pMR4mI+N/H1I1ynHwXnO6FowXJc0=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.3/go.mod h1:7UQ/e69kU7LDPtY40OyoHYgRmgfGM4mgsLYtcObdveU=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.3/go.mod h1:bfBj0iVmsUyUg4weDB4NxktD9rDGeKSVWnjTnwbx9b8=
+github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
 github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
 github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 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/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
-github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
+github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
+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=
-github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
 github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@@ -171,63 +203,57 @@
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
-github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
+github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
 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/tableflip v0.0.0-20190329062924-8392f1641731/go.mod h1:erh4dYezoMVbIa52pi7i1Du7+cXOgqNuTamt10qvMoA=
-github.com/cloudflare/tableflip v1.2.2 h1:WkhiowHlg0nZuH7Y2beLVIZDfxtSvKta1f22PEgUN7w=
-github.com/cloudflare/tableflip v1.2.2/go.mod h1:P4gRehmV6Z2bY5ao5ml9Pd8u6kuEnlB37pUFMmv7j2E=
+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=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
 github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
-github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
-github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
-github.com/containerd/cgroups v0.0.0-20201118023556-2819c83ced99/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
+github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
-github.com/coreos/go-systemd/v22 v22.3.1/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
-github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU=
 github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
 github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
 github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
 github.com/dgryski/go-minhash v0.0.0-20170608043002-7fe510aff544/go.mod h1:VBi0XHpFy0xiMySf6YpVbRqrupW4RprJ5QTyN+XvGSM=
 github.com/dgryski/go-spooky v0.0.0-20170606183049-ed3d087f40e2/go.mod h1:hgHYKsoIw7S/hlWtP7wD1wZ7SX1jPTtKko5X9jrOgPQ=
 github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
 github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
+github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/dpotapov/go-spnego v0.0.0-20190506202455-c2c609116ad0/go.mod h1:P4f4MSk7h52F2PK0lCapn5+fu47Uf8aRdxDSqgezxZE=
-github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dpotapov/go-spnego v0.0.0-20210315154721-298b63a54430/go.mod h1:AVSs/gZKt1bOd2AhkhbS7Qh56Hv7klde22yXVbwYJhc=
+github.com/dpotapov/go-spnego v0.0.0-20220426193508-b7f82e4507db/go.mod h1:AVSs/gZKt1bOd2AhkhbS7Qh56Hv7klde22yXVbwYJhc=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
-github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
-github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
-github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
 github.com/ekzhu/minhash-lsh v0.0.0-20171225071031-5c06ee8586a1/go.mod h1:yEtCVi+QamvzjEH4U/m6ZGkALIkF2xfQnFp0BcKmIOk=
 github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
-github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -235,41 +261,38 @@
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
-github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
-github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
-github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
-github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
+github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
-github.com/getsentry/raven-go v0.1.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
-github.com/getsentry/raven-go v0.1.2/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
-github.com/getsentry/sentry-go v0.5.1/go.mod h1:B8H7x8TYDPkeWPRzGpIiFO97LZP6rL8A3hEt8lUItMw=
-github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
-github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
 github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
-github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
 github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
+github.com/gin-gonic/gin v1.7.3/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
 github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
-github.com/git-lfs/git-lfs v1.5.1-0.20210304194248-2e1d981afbe3/go.mod h1:8Xqs4mqL7o6xEnaXckIgELARTeK7RYtm3pBab7S79Js=
-github.com/git-lfs/gitobj/v2 v2.0.1/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
-github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
-github.com/git-lfs/wildmatch v1.0.4/go.mod h1:SdHAGnApDpnFYQ0vAxbniWR0sn7yLJ3QXo9RRfhn2ew=
+github.com/git-lfs/git-lfs/v3 v3.2.0/go.mod h1:GZZO3jw2Yn3/1KFV4nRoXUzH+yPzGypIdTeQpkzxEvQ=
+github.com/git-lfs/gitobj/v2 v2.1.0/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
+github.com/git-lfs/go-netrc v0.0.0-20210914205454-f0c862dd687a/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
+github.com/git-lfs/pktline v0.0.0-20210330133718-06e9096e2825/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
+github.com/git-lfs/wildmatch/v2 v2.0.1/go.mod h1:EVqonpk9mXbREP3N8UkwoWdrF249uHpCUo5CPXY81gw=
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
+github.com/go-enry/go-enry/v2 v2.8.2/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ=
 github.com/go-enry/go-license-detector/v4 v4.3.0/go.mod h1:HaM4wdNxSlz/9Gw0uVOKSQS5JVFqf2Pk8xUPEn6bldI=
+github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
 github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
@@ -281,10 +304,10 @@
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY=
 github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@@ -297,42 +320,36 @@
 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
-github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
-github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
-github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
-github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
-github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
+github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs=
+github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY=
+github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc=
 github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
 github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
-github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE=
 github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
-github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
 github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
 github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
 github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@@ -361,7 +378,6 @@
 github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -377,11 +393,13 @@
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
-github.com/google/go-replayers/grpcreplay v1.0.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
-github.com/google/go-replayers/httpreplay v0.1.2/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
+github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk=
+github.com/google/go-replayers/httpreplay v1.1.1/go.mod h1:gN9GeLIs7l6NUoVaSSnv2RiqK1NiwAmD0MrKeC9IIks=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -390,6 +408,7 @@
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
+github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -403,6 +422,7 @@
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210125172800-10e9aeb4a998/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
@@ -411,33 +431,30 @@
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
-github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
 github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
+github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
+github.com/googleapis/gax-go/v2 v2.2.0 h1:s7jOdKSaksJVOxE0Y/S32otcfiP+UQ0cL8/GTKaONwE=
+github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
-github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-middleware v1.2.3-0.20210213123510-be4c235f9d1c/go.mod h1:RXwzibsL7UhPcEmGyGvXKJ8kyJsOCOEaLgGce4igMFs=
 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
-github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
+github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
 github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
@@ -448,6 +465,8 @@
 github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -458,22 +477,21 @@
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
-github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 h1:Y4V+SFe7d3iH+9pJCoeWIOS5/xBJIFsltS7E+KJSsJY=
-github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
+github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
+github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
 github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b/go.mod h1:HmaZGXHdSwQh1jnUlBGN2BeEYOHACLVGzYOXCbsLvxY=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
+github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
 github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
-github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
 github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
 github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
 github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
@@ -486,7 +504,8 @@
 github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
 github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
 github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
-github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
 github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
 github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
 github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
@@ -500,39 +519,42 @@
 github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
 github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
 github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
 github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
 github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
-github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
 github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
 github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
 github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
-github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
+github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
+github.com/jackc/pgx/v4 v4.17.0/go.mod h1:Gd6RmOhtFLTu8cp/Fhq4kP195KrshxYJH3oW8AWJ1pw=
 github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
-github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
+github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
 github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
+github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
+github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 github.com/jdkato/prose v1.1.0/go.mod h1:jkF0lkxaX5PFSlk9l4Gh9Y+T57TqUZziWT7uZbW5ADg=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
 github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
-github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -541,38 +563,30 @@
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jteeuwen/go-bindata v3.0.8-0.20180305030458-6025e8de665b+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
-github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
-github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
-github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
-github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk=
+github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
 github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
-github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U=
 github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
-github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw=
 github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
-github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
 github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
 github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
-github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
+github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
 github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
-github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
@@ -582,30 +596,27 @@
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
+github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
 github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/leonelquinteros/gotext v1.5.0/go.mod h1:OCiUVHuhP9LGFBQ1oAmdtNCHJCiHiQA8lf4nAifHkr0=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
-github.com/libgit2/git2go/v31 v31.4.12/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec=
+github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go/v33 v33.0.9/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
-github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
-github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7/go.mod h1:Spd59icnvRxSKuyijbbwe5AemzvcyXAUBgApa7VybMw=
-github.com/lightstep/lightstep-tracer-go v0.15.6/go.mod h1:6AMpwZpsyCFwSovxzM78e+AsYxE8sGwiM6C3TytaWeI=
-github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/lightstep/lightstep-tracer-go v0.24.0/go.mod h1:RnONwHKg89zYPmF+Uig5PpHMUcYCFgml8+r4SS53y7A=
 github.com/lightstep/lightstep-tracer-go v0.25.0 h1:sGVnz8h3jTQuHKMbUe2949nXm3Sg09N1UcR3VoQNN5E=
 github.com/lightstep/lightstep-tracer-go v0.25.0/go.mod h1:G1ZAEaqTHFPWpWunnbUn1ADEY/Jvzz7jIOaXwAfD6A8=
-github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
+github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=
+github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -613,6 +624,7 @@
 github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
+github.com/mattn/go-ieproxy v0.0.3/go.mod h1:6ZpRmhBaYuBX1U2za+9rC9iCGLsSp2tftelZne7CPko=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@@ -621,23 +633,23 @@
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
-github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
-github.com/mattn/go-shellwords v0.0.0-20190425161501-2444a32a19f4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
+github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-shellwords v1.0.11 h1:vCoR9VPpsk/TZFW2JwK5I9S0xdrtUq2bph6/YjEPnaw=
 github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
-github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg=
-github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
 github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
 github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
 github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
 github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
@@ -647,33 +659,27 @@
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
 github.com/montanaflynn/stats v0.0.0-20151014174947-eeaced052adb/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
-github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
-github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
-github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
 github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
-github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
 github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
-github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
-github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc=
 github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
-github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
-github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
-github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -682,111 +688,82 @@
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
-github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
-github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
 github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
-github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
-github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
-github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
-github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
-github.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc=
 github.com/otiai10/copy v1.4.2 h1:RTiz2sol3eoXPLF4o+YWqEybwfUa/Q2Nkc4ZIUs3fwI=
 github.com/otiai10/copy v1.4.2/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E=
 github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
 github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
-github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw=
 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
 github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E=
 github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
-github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
-github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
-github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
-github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml/v2 v2.0.3/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
 github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
 github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
-github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
-github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
-github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
 github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
+github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
 github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
+github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
+github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
 github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
 github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
-github.com/rubenv/sql-migrate v0.0.0-20191213152630-06338513c237/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc=
+github.com/rubenv/sql-migrate v1.1.2/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ=
 github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086/go.mod h1:YpdgDXpumPB/+EGmGTYHeiW/0QVFRzBYTNFaxWfPDk4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
-github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/sebest/xff v0.0.0-20160910043805-6c115e0ffa35/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
-github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY=
-github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
 github.com/shirou/gopsutil/v3 v3.21.2 h1:fIOk3hyqV1oGKogfGNjUZa0lUbtlkx3+ZT0IoJth2uM=
 github.com/shirou/gopsutil/v3 v3.21.2/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
 github.com/shogo82148/go-shuffle v0.0.0-20170808115208-59829097ff3b/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
@@ -795,67 +772,60 @@
 github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
 github.com/ssgelm/cookiejarparser v1.0.1/go.mod h1:DUfC0mpjIzlDN7DzKjXpHj0qMI5m9VrZuz3wSlI+OEI=
-github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
-github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
-github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
-github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
 github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=
 github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
 github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc=
 github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
-github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-client-go v2.27.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-client-go v2.29.1+incompatible h1:R9ec3zO3sGpzs0abd43Y+fBZRJ9uiH6lXyR/+u6brW4=
 github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
+github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
+github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
 github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
 github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
-github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
-github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
-github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
@@ -868,41 +838,28 @@
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v0.0.0-20170210233622-6b67b3fab74d/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
 github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
 github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
-github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
-gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
-gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
-gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
-gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059 h1:X7+3GQIxUpScXpIMCU5+sfpYvZyBIQ3GMlEosP7Jssw=
-gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
-gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
-gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2 h1:zz/4sD22gZqvAdBgd6gYLRIbvrgoQLDE7emPK0sXC6o=
-gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
+gitlab.com/gitlab-org/gitaly/v15 v15.4.0-rc2 h1:ngV/NWEMz4TRlnJFzBpatdXkEIDMHsU3YUzGdZU63qY=
+gitlab.com/gitlab-org/gitaly/v15 v15.4.0-rc2/go.mod h1:Rv/LoDU5I3cOP6KLUWFjC1eigcay2u8b8ZcsW86A0Xo=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed h1:aXSyBpG6K/QsTGevZnpFoDR7Nwvn24RpkDoWe37B8eY=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
-gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
-gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
-gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
-gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
 gitlab.com/gitlab-org/labkit v1.16.0 h1:Vm3NAMZ8RqAunXlvPWby3GJ2R35vsYGP6Uu0YjyMIlY=
 gitlab.com/gitlab-org/labkit v1.16.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
-go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
+go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
+go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
-go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
-go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -917,20 +874,24 @@
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
-go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
+go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
 go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
 go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
-go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
-gocloud.dev v0.23.0/go.mod h1:zklCCIIo1N9ELkU2S2E7tW8P8eeMU7oGLeQCXdDwx9Q=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+gocloud.dev v0.26.0/go.mod h1:mkUgejbnbLotorqDyvedJO20XcZNTynmSeVSQS9btVg=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -948,7 +909,6 @@
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -960,7 +920,6 @@
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@@ -973,16 +932,15 @@
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -995,7 +953,6 @@
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -1005,7 +962,6 @@
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -1023,13 +979,20 @@
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
+golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA=
+golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1042,12 +1005,17 @@
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a h1:4Kd8OPUx1xgUwrHDaviWZO8MsgoZTZYC3g+8m16RBww=
 golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM=
+golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1058,16 +1026,15 @@
 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1077,33 +1044,28 @@
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1122,20 +1084,14 @@
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1144,13 +1100,30 @@
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
+golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1161,18 +1134,16 @@
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
-golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
+golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -1193,12 +1164,10 @@
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -1213,6 +1182,7 @@
 golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
@@ -1238,8 +1208,8 @@
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1252,13 +1222,10 @@
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
-google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
@@ -1277,31 +1244,42 @@
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
-google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA=
+google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
 google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
 google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
 google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
 google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
 google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
-google.golang.org/api v0.54.0 h1:ECJUVngj71QI6XEm7b1sAf8BljU5inEhMbKPR8Lxhhk=
 google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
+google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
+google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E=
+google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
+google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
+google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
+google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM=
+google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M=
+google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
+google.golang.org/api v0.68.0/go.mod h1:sOM8pTpwgflXRhz+oC8H2Dr+UcbMqkPPWNJo88Q7TH8=
+google.golang.org/api v0.69.0/go.mod h1:boanBiw+h5c3s+tBPgEzLDRHfFLWV0qXxRHz3ws7C80=
+google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
+google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
+google.golang.org/api v0.74.0 h1:ExR2D+5TYIrMphWgs5JCgwRhEDlPDXXrLwHHMgPHTXE=
+google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
@@ -1341,12 +1319,9 @@
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
-google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210420162539-3c870d7478d2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210517163617-5e0236093d7a/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
@@ -1355,19 +1330,43 @@
 google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
 google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
 google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
-google.golang.org/genproto v0.0.0-20210813162853-db860fec028c h1:iLQakcwWG3k/++1q/46apVb1sUQ3IqIdn9yUE6eh/xA=
 google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
-google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
-google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
+google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220204002441-d6cc3cc0770e/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220216160803-4663080d8bc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
+google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de h1:9Ti5SG2U4cAcluryUo/sFay3TQKoxiFMfaT0pbizU7k=
+google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
@@ -1388,8 +1387,12 @@
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
 google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
-google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
 google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
+google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w=
+google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -1403,10 +1406,10 @@
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-gopkg.in/DataDog/dd-trace-go.v1 v1.7.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
-gopkg.in/DataDog/dd-trace-go.v1 v1.30.0/go.mod h1:SnKViq44dv/0gjl9RpkP0Y2G3BJSRkp6eYdCSu39iI8=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 h1:DkD0plWEVUB8v/Ru6kRBW30Hy/fRNBC8hPdcExuBZMc=
 gopkg.in/DataDog/dd-trace-go.v1 v1.32.0/go.mod h1:wRKMf/tRASHwH/UOfPQ3IQmVFhTz2/1a1/mpXoIjF54=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
@@ -1416,28 +1419,17 @@
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
-gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
-gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
-gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
 gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
 gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
-gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
-gopkg.in/jcmturner/goidentity.v2 v2.0.0/go.mod h1:vCwK9HeXksMeUmQ4SxDd1tRz4LejrKh3KRVjQWhjvZI=
-gopkg.in/jcmturner/gokrb5.v5 v5.3.0/go.mod h1:oQz8Wc5GsctOTgCVyKad1Vw4TCWz5G6gfIQr88RPv4k=
-gopkg.in/jcmturner/rpc.v0 v0.0.2/go.mod h1:NzMq6cRzR9lipgw7WxRBHNx5N8SifBuaCQsOT1kWY/E=
+gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1448,9 +1440,10 @@
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1458,11 +1451,8 @@
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
 nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
-sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
diff --git a/internal/command/README.md b/internal/command/README.md
new file mode 100644
--- /dev/null
+++ b/internal/command/README.md
@@ -0,0 +1,30 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Overview
+
+This package consists of a set of packages that are responsible for executing a particular command/feature/operation.
+The full list of features can be viewed [here](https://gitlab.com/gitlab-org/gitlab-shell/-/blob/main/doc/features.md).
+The commands implement the common interface:
+
+```go
+type Command interface {
+	Execute(ctx context.Context) error
+}
+```
+
+A command is executed by running the `Execute` method. The execution logic mostly shares the common pattern:
+
+- Parse the arguments and validate them.
+- Communicate with GitLab Rails using [gitlabnet](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/gitlabnet) package. For example, it can be checking whether a client is authorized to execute this particular command or asking for a personal access token in order to return it to the client.
+- If a command is related to Git operations, establish a connection with Gitaly using [handler](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/handler) and [gitaly](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/gitaly) packages and provide two-way communication between Gitaly and the client.
+- Return results to the client.
+
+This package is being used to build a particular command based on the passed arguments in the following files that are under `cmd` directory:
+- [cmd/gitlab-shell/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/gitlab-shell/command)
+- [cmd/check/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/check/command)
+- [cmd/gitlab-shell-authorized-keys-check/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/gitlab-shell-authorized-keys-check/command)
+- [cmd/gitlab-shell-authorized-principals-check/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/gitlab-shell-authorized-principals-check/command)
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -5,8 +5,8 @@
 
 	"google.golang.org/grpc"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -5,8 +5,8 @@
 
 	"google.golang.org/grpc"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -5,8 +5,8 @@
 
 	"google.golang.org/grpc"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
--- a/internal/gitaly/gitaly.go
+++ b/internal/gitaly/gitaly.go
@@ -9,9 +9,9 @@
 	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
 	"google.golang.org/grpc"
 
-	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	gitalyclient "gitlab.com/gitlab-org/gitaly/v14/client"
+	gitalyauth "gitlab.com/gitlab-org/gitaly/v15/auth"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	gitalyclient "gitlab.com/gitlab-org/gitaly/v15/client"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
 	"gitlab.com/gitlab-org/labkit/log"
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -5,7 +5,7 @@
 	"fmt"
 	"net/http"
 
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -10,7 +10,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -16,7 +16,7 @@
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -11,7 +11,7 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1681211921 -7200
#      Tue Apr 11 13:18:41 2023 +0200
# Branch heptapod
# Node ID b6d4930111c1dc7f4662ed98544211d2b0ad3593
# Parent  8ecde27776526bb72c31795c54d68ac62e3b8a9d
CI/CD: testing with Golang 1.19

This is following upstream changes at v14.12.0 in .gitlab-ci.yml

diff --git a/heptapod-ci.yml b/heptapod-ci.yml
--- a/heptapod-ci.yml
+++ b/heptapod-ci.yml
@@ -43,7 +43,7 @@
   image: golang:${GO_VERSION}
   parallel:
     matrix:
-      - GO_VERSION: ["1.17", "1.18"]
+      - GO_VERSION: ["1.17", "1.18", "1.19"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1681212146 -7200
#      Tue Apr 11 13:22:26 2023 +0200
# Branch heptapod
# Node ID 4879c15232ec66f811816160bc46f21db45c3f9b
# Parent  b6d4930111c1dc7f4662ed98544211d2b0ad3593
CI/CD: bumping Gitaly version to match GitLab 15.5

Upstream configuration `.gitlab-ci.yml` is actually using `master`,
which obviously cannot suit us. Still, we were on a really old
Gitaly version.

diff --git a/heptapod-ci.yml b/heptapod-ci.yml
--- a/heptapod-ci.yml
+++ b/heptapod-ci.yml
@@ -31,7 +31,7 @@
     - go version
     - which go
   services:
-    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:v14.10.1
+    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:v15.5.0
       # Disable the hooks so we don't have to stub the GitLab API
       command: ["bash", "-c", "mkdir -p /home/git/repositories && rm -rf /srv/gitlab-shell/hooks/* && exec /usr/bin/env GITALY_TESTING_NO_GIT_HOOKS=1 /scripts/process-wrapper"]
       alias: gitaly
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1681429800 -7200
#      Fri Apr 14 01:50:00 2023 +0200
# Branch heptapod
# Node ID 49a2ffd1d601b19efbf9ec46480a2491937306fe
# Parent  4879c15232ec66f811816160bc46f21db45c3f9b
Added tag heptapod-14.12.0 for changeset 4879c15232ec

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -52,3 +52,4 @@
 cfb031a1e9de24b88498850cff796c224ba48edd heptapod-14.7.1
 cf818cb4e4fe5440a953da19e2e8650bddbf45d5 heptapod-14.9.0
 c6529456e762c07071757b58ff7e1cc87b6947da heptapod-14.10.0
+4879c15232ec66f811816160bc46f21db45c3f9b heptapod-14.12.0
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1683116023 -7200
#      Wed May 03 14:13:43 2023 +0200
# Branch heptapod-stable
# Node ID 5603c54514186b70bfd3a4013d8eace4811cc85a
# Parent  afce5deb7f84e3e4259b43e9f1eee78f0df83e8d
# Parent  49a2ffd1d601b19efbf9ec46480a2491937306fe
heptapod#743: making 0.36 the new stable

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -61,7 +61,7 @@
   image: golang:${GO_VERSION}
   parallel:
     matrix:
-      - GO_VERSION: ["1.17", "1.18"]
+      - GO_VERSION: ["1.17", "1.18", "1.19"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -52,3 +52,4 @@
 cfb031a1e9de24b88498850cff796c224ba48edd heptapod-14.7.1
 cf818cb4e4fe5440a953da19e2e8650bddbf45d5 heptapod-14.9.0
 c6529456e762c07071757b58ff7e1cc87b6947da heptapod-14.10.0
+4879c15232ec66f811816160bc46f21db45c3f9b heptapod-14.12.0
diff --git a/.tool-versions b/.tool-versions
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 2.7.5
-golang 1.17.9
+golang 1.18.6
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,15 @@
+v14.12.0
+
+- Trim secret before signing JWT tokens !686
+- Bump .tool-versions to use Go 1.18.6 !685
+- Update Gitaly to 15.4.0-rc2 !681
+- Test against Golang v1.19 !680
+
+v14.11.0
+
+- Update Gitaly to v15 !676
+- Fixed extra slashes in API request paths generated for geo !673
+
 v14.10.0
 
 - Implement Push Auth support for 2FA verification !454
diff --git a/Dangerfile b/Dangerfile
--- a/Dangerfile
+++ b/Dangerfile
@@ -5,5 +5,5 @@
 Gitlab::Dangerfiles.for_project(self) do |gitlab_dangerfiles|
   gitlab_dangerfiles.import_plugins
 
-  gitlab_dangerfiles.import_dangerfiles(except: %w[changelog commit_messages simple_roulette])
+  gitlab_dangerfiles.import_dangerfiles(except: %w[changelog commit_messages])
 end
diff --git a/Gemfile b/Gemfile
--- a/Gemfile
+++ b/Gemfile
@@ -5,5 +5,5 @@
 end
 
 group :development, :danger do
-  gem 'gitlab-dangerfiles', '~> 3.0.0'
+  gem 'gitlab-dangerfiles', '~> 3.5.1'
 end
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -11,7 +11,7 @@
     colored2 (3.1.2)
     cork (0.3.0)
       colored2 (~> 3.1)
-    danger (8.5.0)
+    danger (8.6.1)
       claide (~> 1.0)
       claide-plugins (>= 0.9.2)
       colored2 (~> 3.1)
@@ -28,7 +28,7 @@
       danger
       gitlab (~> 4.2, >= 4.2.0)
     diff-lcs (1.3)
-    faraday (1.10.0)
+    faraday (1.10.1)
       faraday-em_http (~> 1.0)
       faraday-em_synchrony (~> 1.0)
       faraday-excon (~> 1.1)
@@ -43,29 +43,29 @@
     faraday-em_http (1.0.0)
     faraday-em_synchrony (1.0.0)
     faraday-excon (1.1.0)
-    faraday-http-cache (2.2.0)
+    faraday-http-cache (2.4.1)
       faraday (>= 0.8)
     faraday-httpclient (1.0.1)
-    faraday-multipart (1.0.3)
-      multipart-post (>= 1.2, < 3)
+    faraday-multipart (1.0.4)
+      multipart-post (~> 2)
     faraday-net_http (1.0.1)
     faraday-net_http_persistent (1.2.0)
     faraday-patron (1.0.0)
     faraday-rack (1.0.0)
     faraday-retry (1.0.3)
-    git (1.10.2)
+    git (1.11.0)
       rchardet (~> 1.8)
-    gitlab (4.18.0)
-      httparty (~> 0.18)
+    gitlab (4.19.0)
+      httparty (~> 0.20)
       terminal-table (>= 1.5.1)
-    gitlab-dangerfiles (3.0.0)
+    gitlab-dangerfiles (3.5.1)
       danger (>= 8.4.5)
       danger-gitlab (>= 8.0.0)
       rake
     httparty (0.20.0)
       mime-types (~> 3.0)
       multi_xml (>= 0.5.2)
-    kramdown (2.3.2)
+    kramdown (2.4.0)
       rexml
     kramdown-parser-gfm (1.1.0)
       kramdown (~> 2.0)
@@ -73,14 +73,14 @@
       mime-types-data (~> 3.2015)
     mime-types-data (3.2022.0105)
     multi_xml (0.6.0)
-    multipart-post (2.1.1)
+    multipart-post (2.2.3)
     nap (1.1.0)
     no_proxy_fix (0.1.2)
-    octokit (4.22.0)
-      faraday (>= 0.9)
-      sawyer (~> 0.8.0, >= 0.5.3)
+    octokit (4.25.1)
+      faraday (>= 1, < 3)
+      sawyer (~> 0.9)
     open4 (1.3.4)
-    public_suffix (4.0.6)
+    public_suffix (4.0.7)
     rake (13.0.6)
     rchardet (1.8.0)
     rexml (3.2.5)
@@ -98,18 +98,18 @@
       rspec-support (~> 3.8.0)
     rspec-support (3.8.0)
     ruby2_keywords (0.0.5)
-    sawyer (0.8.2)
+    sawyer (0.9.2)
       addressable (>= 2.3.5)
-      faraday (> 0.8, < 2.0)
+      faraday (>= 0.17.3, < 3)
     terminal-table (3.0.2)
       unicode-display_width (>= 1.1.1, < 3)
-    unicode-display_width (2.1.0)
+    unicode-display_width (2.2.0)
 
 PLATFORMS
   ruby
 
 DEPENDENCIES
-  gitlab-dangerfiles (~> 3.0.0)
+  gitlab-dangerfiles (~> 3.5.1)
   rspec (~> 3.8.0)
 
 BUNDLED WITH
diff --git a/HEPTAPOD_CHANGELOG b/HEPTAPOD_CHANGELOG
--- a/HEPTAPOD_CHANGELOG
+++ b/HEPTAPOD_CHANGELOG
@@ -10,6 +10,10 @@
 
 The corresponding tag is heptapod-x.y.z
 
+## 14.12.0
+
+- Bump to upstream GitLab Shell v14.12.0 (GitLab 15.5)
+
 ## 14.10.0
 
 - Bump to upstream GitLab Shell v14.10.0 (GitLab 15.3 and 15.4 series)
diff --git a/HEPTAPOD_VERSION b/HEPTAPOD_VERSION
--- a/HEPTAPOD_VERSION
+++ b/HEPTAPOD_VERSION
@@ -1,1 +1,1 @@
-14.10.0
+14.12.0
diff --git a/VERSION b/VERSION
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-14.10.0
+14.12.0
diff --git a/client/client_test.go b/client/client_test.go
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -20,7 +20,7 @@
 )
 
 var (
-	secret = []byte("sssh, it's a secret")
+	secret = "sssh, it's a secret"
 )
 
 func TestClients(t *testing.T) {
@@ -31,24 +31,29 @@
 		relativeURLRoot string
 		caFile          string
 		server          func(*testing.T, []testserver.TestRequestHandler) string
+		secret          string
 	}{
 		{
 			desc:   "Socket client",
 			server: testserver.StartSocketHttpServer,
+			secret: secret,
 		},
 		{
 			desc:            "Socket client with a relative URL at /",
 			relativeURLRoot: "/",
 			server:          testserver.StartSocketHttpServer,
+			secret:          secret,
 		},
 		{
 			desc:            "Socket client with relative URL at /gitlab",
 			relativeURLRoot: "/gitlab",
 			server:          testserver.StartSocketHttpServer,
+			secret:          secret,
 		},
 		{
 			desc:   "Http client",
 			server: testserver.StartHttpServer,
+			secret: secret,
 		},
 		{
 			desc:   "Https client",
@@ -56,6 +61,15 @@
 			server: func(t *testing.T, handlers []testserver.TestRequestHandler) string {
 				return testserver.StartHttpsServer(t, handlers, "")
 			},
+			secret: secret,
+		},
+		{
+			desc:   "Secret with newlines",
+			caFile: path.Join(testhelper.TestRoot, "certs/valid/server.crt"),
+			server: func(t *testing.T, handlers []testserver.TestRequestHandler) string {
+				return testserver.StartHttpsServer(t, handlers, "")
+			},
+			secret: "\n" + secret + "\n",
 		},
 	}
 
@@ -66,7 +80,7 @@
 			httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", 1, nil)
 			require.NoError(t, err)
 
-			client, err := NewGitlabNetClient("", "", string(secret), httpClient)
+			client, err := NewGitlabNetClient("", "", tc.secret, httpClient)
 			require.NoError(t, err)
 
 			testBrokenRequest(t, client)
@@ -74,9 +88,10 @@
 			testSuccessfulPost(t, client)
 			testMissing(t, client)
 			testErrorMessage(t, client)
-			testAuthenticationHeader(t, client)
+			testAuthenticationHeader(t, tc.secret, client)
 			testJWTAuthenticationHeader(t, client)
 			testXForwardedForHeader(t, client)
+			testHostWithTrailingSlash(t, client)
 		})
 	}
 }
@@ -153,7 +168,7 @@
 	})
 }
 
-func testAuthenticationHeader(t *testing.T, client *GitlabNetClient) {
+func testAuthenticationHeader(t *testing.T, secret string, client *GitlabNetClient) {
 	t.Run("Authentication headers for GET", func(t *testing.T) {
 		response, err := client.Get(context.Background(), "/auth")
 		require.NoError(t, err)
@@ -166,7 +181,7 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, secret, header)
+		require.Equal(t, secret, string(header))
 	})
 
 	t.Run("Authentication headers for POST", func(t *testing.T) {
@@ -181,7 +196,7 @@
 
 		header, err := base64.StdEncoding.DecodeString(string(responseBody))
 		require.NoError(t, err)
-		require.Equal(t, secret, header)
+		require.Equal(t, secret, string(header))
 	})
 }
 
@@ -192,7 +207,7 @@
 
 		claims := &jwt.RegisteredClaims{}
 		token, err := jwt.ParseWithClaims(string(responseBody), claims, func(token *jwt.Token) (interface{}, error) {
-			return secret, nil
+			return []byte(secret), nil
 		})
 		require.NoError(t, err)
 		require.True(t, token.Valid)
@@ -237,6 +252,16 @@
 	})
 }
 
+func testHostWithTrailingSlash(t *testing.T, client *GitlabNetClient) {
+	oldHost := client.httpClient.Host
+	client.httpClient.Host = oldHost + "/"
+
+	testSuccessfulGet(t, client)
+	testSuccessfulPost(t, client)
+
+	client.httpClient.Host = oldHost
+}
+
 func buildRequests(t *testing.T, relativeURLRoot string) []testserver.TestRequestHandler {
 	requests := []testserver.TestRequestHandler{
 		{
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -85,6 +85,10 @@
 	return path
 }
 
+func appendPath(host string, path string) string {
+	return strings.TrimSuffix(host, "/") + "/" + strings.TrimPrefix(path, "/")
+}
+
 func newRequest(ctx context.Context, method, host, path string, data interface{}) (*http.Request, error) {
 	var jsonReader io.Reader
 	if data != nil {
@@ -96,7 +100,7 @@
 		jsonReader = bytes.NewReader(jsonData)
 	}
 
-	request, err := http.NewRequestWithContext(ctx, method, host+path, jsonReader)
+	request, err := http.NewRequestWithContext(ctx, method, appendPath(host, path), jsonReader)
 	if err != nil {
 		return nil, err
 	}
@@ -137,9 +141,7 @@
 	if user != "" && password != "" {
 		request.SetBasicAuth(user, password)
 	}
-	secretBytes := []byte(c.secret)
-
-	encodedSecret := base64.StdEncoding.EncodeToString(secretBytes)
+	encodedSecret := base64.StdEncoding.EncodeToString([]byte(c.secret))
 	request.Header.Set(secretHeaderName, encodedSecret)
 
 	claims := jwt.RegisteredClaims{
@@ -147,6 +149,7 @@
 		IssuedAt:  jwt.NewNumericDate(time.Now()),
 		ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtTTL)),
 	}
+	secretBytes := []byte(strings.TrimSpace(c.secret))
 	tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secretBytes)
 	if err != nil {
 		return nil, err
diff --git a/client/testserver/gitalyserver.go b/client/testserver/gitalyserver.go
--- a/client/testserver/gitalyserver.go
+++ b/client/testserver/gitalyserver.go
@@ -10,8 +10,8 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/labkit/log"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials/insecure"
diff --git a/cmd/gitlab-sshd/acceptance_test.go b/cmd/gitlab-sshd/acceptance_test.go
--- a/cmd/gitlab-sshd/acceptance_test.go
+++ b/cmd/gitlab-sshd/acceptance_test.go
@@ -22,8 +22,8 @@
 	"github.com/mikesmitty/edkey"
 	"github.com/pires/go-proxyproto"
 	"github.com/stretchr/testify/require"
-	gitalyClient "gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	gitalyClient "gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/testhelper"
 	"golang.org/x/crypto/ssh"
 )
@@ -387,12 +387,25 @@
 	ensureGitalyRepository(t)
 
 	client := runSSHD(t, successAPI(t))
-
 	session, err := client.NewSession()
 	require.NoError(t, err)
 	defer session.Close()
 
-	output, err := session.Output(fmt.Sprintf("git-receive-pack %s", testRepo))
+	stdin, err := session.StdinPipe()
+	require.NoError(t, err)
+
+	stdout, err := session.StdoutPipe()
+	require.NoError(t, err)
+
+	err = session.Start(fmt.Sprintf("git-receive-pack %s", testRepo))
+	require.NoError(t, err)
+
+	// Gracefully close connection
+	_, err = fmt.Fprintln(stdin, "0000")
+	require.NoError(t, err)
+	stdin.Close()
+
+	output, err := io.ReadAll(stdout)
 	require.NoError(t, err)
 
 	outputLines := strings.Split(string(output), "\n")
diff --git a/doc/beginners_guide.md b/doc/beginners_guide.md
--- a/doc/beginners_guide.md
+++ b/doc/beginners_guide.md
@@ -1,7 +1,7 @@
 ---
 stage: Create
 group: Source Code
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
 ---
 
 # Beginner's guide to GitLab Shell contributions
diff --git a/doc/features.md b/doc/features.md
--- a/doc/features.md
+++ b/doc/features.md
@@ -1,7 +1,7 @@
 ---
 stage: Create
 group: Source Code
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
 ---
 
 # Feature list
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -10,27 +10,29 @@
 	github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
 	github.com/otiai10/copy v1.4.2
 	github.com/pires/go-proxyproto v0.6.2
-	github.com/prometheus/client_golang v1.12.1
-	github.com/sirupsen/logrus v1.8.1
-	github.com/stretchr/testify v1.7.0
-	gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
+	github.com/prometheus/client_golang v1.12.2
+	github.com/sirupsen/logrus v1.9.0
+	github.com/stretchr/testify v1.8.0
+	gitlab.com/gitlab-org/gitaly/v15 v15.4.0-rc2
 	gitlab.com/gitlab-org/labkit v1.16.0
-	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
-	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	google.golang.org/grpc v1.40.0
+	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
+	golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
+	google.golang.org/grpc v1.48.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
 require (
-	cloud.google.com/go v0.92.2 // indirect
+	cloud.google.com/go v0.100.2 // indirect
+	cloud.google.com/go/compute v1.5.0 // indirect
+	cloud.google.com/go/monitoring v1.4.0 // indirect
 	cloud.google.com/go/profiler v0.1.0 // indirect
-	cloud.google.com/go/trace v0.1.0 // indirect
-	contrib.go.opencensus.io/exporter/stackdriver v0.13.8 // indirect
+	cloud.google.com/go/trace v1.2.0 // indirect
+	contrib.go.opencensus.io/exporter/stackdriver v0.13.10 // indirect
 	github.com/DataDog/datadog-go v4.4.0+incompatible // indirect
 	github.com/DataDog/sketches-go v1.0.0 // indirect
 	github.com/Microsoft/go-winio v0.5.0 // indirect
 	github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
-	github.com/aws/aws-sdk-go v1.38.35 // indirect
+	github.com/aws/aws-sdk-go v1.43.31 // indirect
 	github.com/beevik/ntp v0.3.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
@@ -41,12 +43,13 @@
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
-	github.com/google/go-cmp v0.5.6 // indirect
+	github.com/google/go-cmp v0.5.8 // indirect
 	github.com/google/pprof v0.0.0-20210804190019-f964ff605595 // indirect
-	github.com/google/uuid v1.2.0 // indirect
-	github.com/googleapis/gax-go/v2 v2.0.5 // indirect
-	github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 // indirect
+	github.com/google/uuid v1.3.0 // indirect
+	github.com/googleapis/gax-go/v2 v2.2.0 // indirect
+	github.com/hashicorp/yamux v0.1.1 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	github.com/kr/text v0.2.0 // indirect
 	github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 // indirect
 	github.com/lightstep/lightstep-tracer-go v0.25.0 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
@@ -63,22 +66,23 @@
 	github.com/tinylib/msgp v1.1.2 // indirect
 	github.com/tklauser/go-sysconf v0.3.4 // indirect
 	github.com/tklauser/numcpus v0.2.1 // indirect
-	github.com/uber/jaeger-client-go v2.29.1+incompatible // indirect
+	github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
 	github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
 	go.opencensus.io v0.23.0 // indirect
-	go.uber.org/atomic v1.7.0 // indirect
-	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
-	golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a // indirect
-	golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
+	go.uber.org/atomic v1.9.0 // indirect
+	golang.org/x/net v0.0.0-20220531201128-c960675eff93 // indirect
+	golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
+	golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
 	golang.org/x/text v0.3.7 // indirect
-	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
+	golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
-	google.golang.org/api v0.54.0 // indirect
+	google.golang.org/api v0.74.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20210813162853-db860fec028c // indirect
-	google.golang.org/protobuf v1.27.1 // indirect
+	google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de // indirect
+	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
+	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
 replace golang.org/x/crypto => gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -1,9 +1,6 @@
-bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
-bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
 cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
@@ -22,104 +19,123 @@
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
 cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.82.0/go.mod h1:vlKccHJGuFBFufnAnuB08dfEH9Y3H7dzDzRECFdC2TA=
 cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
 cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
 cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
 cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
-cloud.google.com/go v0.92.2 h1:podK44+0gcW5rWGMjJiPH0+rzkCTQx/zT0qF5CLqVkM=
 cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
+cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
+cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
+cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U=
+cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y=
+cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
+cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw=
+cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
+cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM=
+cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
+cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=
+cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw=
+cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc=
+cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
+cloud.google.com/go/kms v1.1.0/go.mod h1:WdbppnCDMDpOvoYBMn1+gNmOeEoZYqAv+HeuKARGCXI=
+cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA=
+cloud.google.com/go/monitoring v1.1.0/go.mod h1:L81pzz7HKn14QCMaCs6NTQkdBnE87TElyanS95vIcl4=
+cloud.google.com/go/monitoring v1.4.0 h1:05+IuNMbh40hbxcqQ4SnynbwZbLG1Wc9dysIJxnfv7U=
+cloud.google.com/go/monitoring v1.4.0/go.mod h1:y6xnxfwI3hTFWOdkOaD7nfJVlwuC3/mS/5kvtT131p4=
 cloud.google.com/go/profiler v0.1.0 h1:MG/rxKC1MztRfEWMGYKFISxyZak5hNh29f0A/z2tvWk=
 cloud.google.com/go/profiler v0.1.0/go.mod h1:D7S7LV/zKbRWkOzYL1b5xytpqt8Ikd/v/yvf1/Tx2pQ=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
 cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
-cloud.google.com/go/pubsub v1.10.3/go.mod h1:FUcc28GpGxxACoklPsE1sCtbkY4Ix+ro7yvw+h82Jn4=
+cloud.google.com/go/pubsub v1.19.0/go.mod h1:/O9kmSe9bb9KRnIAWkzmqhPjHo6LtzGOBYd/kr06XSs=
+cloud.google.com/go/secretmanager v1.3.0/go.mod h1:+oLTkouyiYiabAQNugCeTS3PAArGiMJuBqvJnJsyH+U=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-cloud.google.com/go/storage v1.15.0 h1:Ljj+ZXVEhCr/1+4ZhvtteN1ND7UUsNTlduGclLh8GO0=
-cloud.google.com/go/storage v1.15.0/go.mod h1:mjjQMoxxyGH7Jr8K5qrx6N2O0AHsczI61sMNn03GIZI=
-cloud.google.com/go/trace v0.1.0 h1:nUGUK79FOkN0UGUXhBmVBkbu1PYsHe0YyFSPLOD9Npg=
+cloud.google.com/go/storage v1.21.0 h1:HwnT2u2D309SFDHQII6m18HlrCi3jAXhUMTLOWXYH14=
+cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKuqnZI01LAA=
 cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g=
+cloud.google.com/go/trace v1.0.0/go.mod h1:4iErSByzxkyHWzzlAj63/Gmjz0NH1ASqhJguHpGcr6A=
+cloud.google.com/go/trace v1.2.0 h1:oIaB4KahkIUOpLSAAjEJ8y2desbjY/x/RfP4O3KAtTI=
+cloud.google.com/go/trace v1.2.0/go.mod h1:Wc8y/uYyOhPy12KEnXG9XGrvfMz5F5SrYecQlbW1rwM=
 contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
-contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
-contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.10 h1:a9+GZPUe+ONKUwULjlEOucMMG0qfSCCenlji0Nhqbys=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.10/go.mod h1:I5htMbyta491eUxufwwZPQdcKvvgzMB4O9ni41YnIM8=
 contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
-github.com/Azure/azure-amqp-common-go/v3 v3.1.0/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0=
+github.com/Azure/azure-amqp-common-go/v3 v3.2.1/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI=
+github.com/Azure/azure-amqp-common-go/v3 v3.2.2/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI=
 github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
 github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
-github.com/Azure/azure-sdk-for-go v54.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
-github.com/Azure/azure-service-bus-go v0.10.11/go.mod h1:AWw9eTTWZVZyvgpPahD1ybz3a8/vT3GsJDS8KYex55U=
-github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nUdBD+e64lEuc4sVnuOfNs=
-github.com/Azure/go-amqp v0.13.0/go.mod h1:qj+o8xPCz9tMSbQ83Vp8boHahuRDl5mkNHyt1xlxUTs=
-github.com/Azure/go-amqp v0.13.4/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
-github.com/Azure/go-amqp v0.13.7/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
+github.com/Azure/azure-sdk-for-go v59.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
+github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
+github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU=
+github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck=
+github.com/Azure/go-amqp v0.16.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
+github.com/Azure/go-amqp v0.16.4/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
 github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
-github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
 github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
-github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
-github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE=
+github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
+github.com/Azure/go-autorest/autorest v0.11.22/go.mod h1:BAWYUWGPEtKPzjVkp0Q6an0MJcJDsoh5Z1BFAEFs4Xs=
 github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
-github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
 github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
-github.com/Azure/go-autorest/autorest/azure/auth v0.5.7/go.mod h1:AkzUsqkrdmNhfP2i54HqINVQopw0CLDnvHpJ88Zz1eI=
+github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
+github.com/Azure/go-autorest/autorest/adal v0.9.17/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.9/go.mod h1:hg3/1yw0Bq87O3KvvnJoAh34/0zbP7SFizX/qN5JvjU=
 github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
 github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
-github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
 github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
 github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
 github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
-github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
 github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
-github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w=
 github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
 github.com/DataDog/datadog-go v4.4.0+incompatible h1:R7WqXWP4fIOAqWJtUKmSfuc7eDsBT58k9AY5WSHVosk=
 github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
 github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
 github.com/DataDog/sketches-go v1.0.0 h1:chm5KSXO7kO+ywGWJ0Zs6tdmWU8PBXSbywFVciL6BG4=
 github.com/DataDog/sketches-go v1.0.0/go.mod h1:O+XkJHWk9w4hDwY2ZUDU31ZC9sNYlYo8DiFsxjYeo1k=
-github.com/GoogleCloudPlatform/cloudsql-proxy v1.22.0/go.mod h1:mAm5O/zik2RFmcpigNjg6nMotDL8ZXJaxKzgGVcSMFA=
-github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
+github.com/GoogleCloudPlatform/cloudsql-proxy v1.29.0/go.mod h1:spvB9eLJH9dutlbPSRmHvSXXHOwGRyeXh1jVdquA2G8=
 github.com/HdrHistogram/hdrhistogram-go v1.1.1 h1:cJXY5VLMHgejurPjZH6Fo9rIwRGLefBGdiaENZALqrg=
 github.com/HdrHistogram/hdrhistogram-go v1.1.1/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
-github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
-github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
-github.com/Microsoft/go-winio v0.4.19/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
 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/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
 github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
-github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
-github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 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=
-github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
-github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
 github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
@@ -128,40 +144,56 @@
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
-github.com/alexbrainman/sspi v0.0.0-20180125232955-4729b3d4d858/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
+github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
-github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
-github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
-github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
 github.com/avast/retry-go v2.4.2+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
-github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
+github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
-github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
-github.com/aws/aws-sdk-go v1.38.35 h1:7AlAO0FC+8nFjxiGKEmq0QLpiA8/XFr6eIxgRTwkdTg=
-github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
-github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
+github.com/aws/aws-sdk-go v1.43.31 h1:yJZIr8nMV1hXjAvvOLUFqZRJcHV7udPQBfhJqawDzI0=
+github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
+github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM=
+github.com/aws/aws-sdk-go-v2/config v1.15.3/go.mod h1:9YL3v07Xc/ohTsxFXzan9ZpFpdTOFl4X65BAKYaz8jg=
+github.com/aws/aws-sdk-go-v2/credentials v1.11.2/go.mod h1:j8YsY9TXTm31k4eFhspiQicfXPLZ0gYXA50i4gxPE8g=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3/go.mod h1:uk1vhHHERfSVCUnqSqz8O48LBYDSC+k6brng09jcMOk=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3/go.mod h1:0dHuD2HZZSiwfJSy1FO5bX1hQ1TxVV1QXXjpn3XUE44=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9/go.mod h1:AnVH5pvai0pAF4lXRq0bmhbes1u9R8wTE+g+183bZNM=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10/go.mod h1:8DcYQcz0+ZJaSxANlHIsbbi6S+zMwjwdDqwW3r9AzaE=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3/go.mod h1:Seb8KNmD6kVTjwRjVEgOT5hPin6sq+v4C2ycJQDwuH8=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3/go.mod h1:wlY6SVjuwvh3TVRpTqdy4I1JpBFLX4UGeKZdWntaocw=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3/go.mod h1:Bm/v2IaN6rZ+Op7zX+bOUMdL4fsrYZiD0dsjLhNKwZc=
+github.com/aws/aws-sdk-go-v2/service/kms v1.16.3/go.mod h1:QuiHPBqlOFCi4LqdSskYYAWpQlx3PKmohy+rE2F+o5g=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z4KJ7DtFFCx8fJD2a491Ak=
+github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4/go.mod h1:PJc8s+lxyU8rrre0/4a0pn2wgwiDvOEzoOjcJUBr67o=
+github.com/aws/aws-sdk-go-v2/service/sns v1.17.4/go.mod h1:kElt+uCcXxcqFyc+bQqZPFD9DME/eC6oHBXvFzQ9Bcw=
+github.com/aws/aws-sdk-go-v2/service/sqs v1.18.3/go.mod h1:skmQo0UPvsjsuYYSYMVmrPc1HWCbHUJyrCEp+ZaLzqM=
+github.com/aws/aws-sdk-go-v2/service/ssm v1.24.1/go.mod h1:NR/xoKjdbRJ+qx0pMR4mI+N/H1I1ynHwXnO6FowXJc0=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.3/go.mod h1:7UQ/e69kU7LDPtY40OyoHYgRmgfGM4mgsLYtcObdveU=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.3/go.mod h1:bfBj0iVmsUyUg4weDB4NxktD9rDGeKSVWnjTnwbx9b8=
+github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
 github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
 github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 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/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
-github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
+github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
+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=
-github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
 github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@@ -171,63 +203,57 @@
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
-github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
+github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
 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/tableflip v0.0.0-20190329062924-8392f1641731/go.mod h1:erh4dYezoMVbIa52pi7i1Du7+cXOgqNuTamt10qvMoA=
-github.com/cloudflare/tableflip v1.2.2 h1:WkhiowHlg0nZuH7Y2beLVIZDfxtSvKta1f22PEgUN7w=
-github.com/cloudflare/tableflip v1.2.2/go.mod h1:P4gRehmV6Z2bY5ao5ml9Pd8u6kuEnlB37pUFMmv7j2E=
+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=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
 github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
-github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
-github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
-github.com/containerd/cgroups v0.0.0-20201118023556-2819c83ced99/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
+github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
-github.com/coreos/go-systemd/v22 v22.3.1/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
-github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU=
 github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
 github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
 github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
 github.com/dgryski/go-minhash v0.0.0-20170608043002-7fe510aff544/go.mod h1:VBi0XHpFy0xiMySf6YpVbRqrupW4RprJ5QTyN+XvGSM=
 github.com/dgryski/go-spooky v0.0.0-20170606183049-ed3d087f40e2/go.mod h1:hgHYKsoIw7S/hlWtP7wD1wZ7SX1jPTtKko5X9jrOgPQ=
 github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
 github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
+github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/dpotapov/go-spnego v0.0.0-20190506202455-c2c609116ad0/go.mod h1:P4f4MSk7h52F2PK0lCapn5+fu47Uf8aRdxDSqgezxZE=
-github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dpotapov/go-spnego v0.0.0-20210315154721-298b63a54430/go.mod h1:AVSs/gZKt1bOd2AhkhbS7Qh56Hv7klde22yXVbwYJhc=
+github.com/dpotapov/go-spnego v0.0.0-20220426193508-b7f82e4507db/go.mod h1:AVSs/gZKt1bOd2AhkhbS7Qh56Hv7klde22yXVbwYJhc=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
-github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
-github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
-github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
 github.com/ekzhu/minhash-lsh v0.0.0-20171225071031-5c06ee8586a1/go.mod h1:yEtCVi+QamvzjEH4U/m6ZGkALIkF2xfQnFp0BcKmIOk=
 github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
-github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -235,41 +261,38 @@
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
-github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
-github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
-github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
-github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
+github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
-github.com/getsentry/raven-go v0.1.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
-github.com/getsentry/raven-go v0.1.2/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
-github.com/getsentry/sentry-go v0.5.1/go.mod h1:B8H7x8TYDPkeWPRzGpIiFO97LZP6rL8A3hEt8lUItMw=
-github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
-github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
 github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
-github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
 github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
+github.com/gin-gonic/gin v1.7.3/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
 github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
-github.com/git-lfs/git-lfs v1.5.1-0.20210304194248-2e1d981afbe3/go.mod h1:8Xqs4mqL7o6xEnaXckIgELARTeK7RYtm3pBab7S79Js=
-github.com/git-lfs/gitobj/v2 v2.0.1/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
-github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
-github.com/git-lfs/wildmatch v1.0.4/go.mod h1:SdHAGnApDpnFYQ0vAxbniWR0sn7yLJ3QXo9RRfhn2ew=
+github.com/git-lfs/git-lfs/v3 v3.2.0/go.mod h1:GZZO3jw2Yn3/1KFV4nRoXUzH+yPzGypIdTeQpkzxEvQ=
+github.com/git-lfs/gitobj/v2 v2.1.0/go.mod h1:q6aqxl6Uu3gWsip5GEKpw+7459F97er8COmU45ncAxw=
+github.com/git-lfs/go-netrc v0.0.0-20210914205454-f0c862dd687a/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
+github.com/git-lfs/pktline v0.0.0-20210330133718-06e9096e2825/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
+github.com/git-lfs/wildmatch/v2 v2.0.1/go.mod h1:EVqonpk9mXbREP3N8UkwoWdrF249uHpCUo5CPXY81gw=
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
+github.com/go-enry/go-enry/v2 v2.8.2/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ=
 github.com/go-enry/go-license-detector/v4 v4.3.0/go.mod h1:HaM4wdNxSlz/9Gw0uVOKSQS5JVFqf2Pk8xUPEn6bldI=
+github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
 github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
@@ -281,10 +304,10 @@
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY=
 github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@@ -297,42 +320,36 @@
 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
-github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
-github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
-github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
-github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
-github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
+github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs=
+github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY=
+github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc=
 github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
 github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
-github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE=
 github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
-github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
 github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
 github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
 github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@@ -361,7 +378,6 @@
 github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -377,11 +393,13 @@
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
-github.com/google/go-replayers/grpcreplay v1.0.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
-github.com/google/go-replayers/httpreplay v0.1.2/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
+github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk=
+github.com/google/go-replayers/httpreplay v1.1.1/go.mod h1:gN9GeLIs7l6NUoVaSSnv2RiqK1NiwAmD0MrKeC9IIks=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -390,6 +408,7 @@
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
+github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -403,6 +422,7 @@
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210125172800-10e9aeb4a998/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
@@ -411,33 +431,30 @@
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
-github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
 github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
+github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
+github.com/googleapis/gax-go/v2 v2.2.0 h1:s7jOdKSaksJVOxE0Y/S32otcfiP+UQ0cL8/GTKaONwE=
+github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
-github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-middleware v1.2.3-0.20210213123510-be4c235f9d1c/go.mod h1:RXwzibsL7UhPcEmGyGvXKJ8kyJsOCOEaLgGce4igMFs=
 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
-github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
+github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
 github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
@@ -448,6 +465,8 @@
 github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -458,22 +477,21 @@
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
-github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 h1:Y4V+SFe7d3iH+9pJCoeWIOS5/xBJIFsltS7E+KJSsJY=
-github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
+github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
+github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
 github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b/go.mod h1:HmaZGXHdSwQh1jnUlBGN2BeEYOHACLVGzYOXCbsLvxY=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
+github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
 github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
-github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
 github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
 github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
 github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
@@ -486,7 +504,8 @@
 github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
 github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
 github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
-github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
 github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
 github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
 github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
@@ -500,39 +519,42 @@
 github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
 github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
 github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
 github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
 github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
-github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
 github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
 github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
 github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
-github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
+github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
+github.com/jackc/pgx/v4 v4.17.0/go.mod h1:Gd6RmOhtFLTu8cp/Fhq4kP195KrshxYJH3oW8AWJ1pw=
 github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
-github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
+github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
 github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
+github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
+github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 github.com/jdkato/prose v1.1.0/go.mod h1:jkF0lkxaX5PFSlk9l4Gh9Y+T57TqUZziWT7uZbW5ADg=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
 github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
-github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -541,38 +563,30 @@
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jteeuwen/go-bindata v3.0.8-0.20180305030458-6025e8de665b+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
-github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
-github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
-github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
-github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk=
+github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
 github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
-github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U=
 github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
-github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw=
 github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
-github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
 github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
 github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
-github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
+github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
 github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
-github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
@@ -582,30 +596,27 @@
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
+github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
 github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/leonelquinteros/gotext v1.5.0/go.mod h1:OCiUVHuhP9LGFBQ1oAmdtNCHJCiHiQA8lf4nAifHkr0=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
-github.com/libgit2/git2go/v31 v31.4.12/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec=
+github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libgit2/git2go/v33 v33.0.9/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
-github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
-github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7/go.mod h1:Spd59icnvRxSKuyijbbwe5AemzvcyXAUBgApa7VybMw=
-github.com/lightstep/lightstep-tracer-go v0.15.6/go.mod h1:6AMpwZpsyCFwSovxzM78e+AsYxE8sGwiM6C3TytaWeI=
-github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/lightstep/lightstep-tracer-go v0.24.0/go.mod h1:RnONwHKg89zYPmF+Uig5PpHMUcYCFgml8+r4SS53y7A=
 github.com/lightstep/lightstep-tracer-go v0.25.0 h1:sGVnz8h3jTQuHKMbUe2949nXm3Sg09N1UcR3VoQNN5E=
 github.com/lightstep/lightstep-tracer-go v0.25.0/go.mod h1:G1ZAEaqTHFPWpWunnbUn1ADEY/Jvzz7jIOaXwAfD6A8=
-github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
+github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=
+github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -613,6 +624,7 @@
 github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
+github.com/mattn/go-ieproxy v0.0.3/go.mod h1:6ZpRmhBaYuBX1U2za+9rC9iCGLsSp2tftelZne7CPko=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@@ -621,23 +633,23 @@
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
-github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
-github.com/mattn/go-shellwords v0.0.0-20190425161501-2444a32a19f4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
+github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-shellwords v1.0.11 h1:vCoR9VPpsk/TZFW2JwK5I9S0xdrtUq2bph6/YjEPnaw=
 github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
-github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg=
-github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
 github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
 github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
 github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
 github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
@@ -647,33 +659,27 @@
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
 github.com/montanaflynn/stats v0.0.0-20151014174947-eeaced052adb/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
-github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
-github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
-github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
 github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
-github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
 github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
-github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
-github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc=
 github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
-github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
-github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
-github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -682,111 +688,82 @@
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
-github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
-github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
 github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
-github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
-github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
-github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
-github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
-github.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc=
 github.com/otiai10/copy v1.4.2 h1:RTiz2sol3eoXPLF4o+YWqEybwfUa/Q2Nkc4ZIUs3fwI=
 github.com/otiai10/copy v1.4.2/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E=
 github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
 github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
-github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw=
 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
 github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E=
 github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
-github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
-github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
-github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
-github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml/v2 v2.0.3/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
 github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
 github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
-github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
-github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
-github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
 github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
+github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
 github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
+github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
+github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
 github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
 github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
-github.com/rubenv/sql-migrate v0.0.0-20191213152630-06338513c237/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc=
+github.com/rubenv/sql-migrate v1.1.2/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ=
 github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086/go.mod h1:YpdgDXpumPB/+EGmGTYHeiW/0QVFRzBYTNFaxWfPDk4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
-github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/sebest/xff v0.0.0-20160910043805-6c115e0ffa35/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM=
 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
-github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY=
-github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
 github.com/shirou/gopsutil/v3 v3.21.2 h1:fIOk3hyqV1oGKogfGNjUZa0lUbtlkx3+ZT0IoJth2uM=
 github.com/shirou/gopsutil/v3 v3.21.2/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
 github.com/shogo82148/go-shuffle v0.0.0-20170808115208-59829097ff3b/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
@@ -795,67 +772,60 @@
 github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
 github.com/ssgelm/cookiejarparser v1.0.1/go.mod h1:DUfC0mpjIzlDN7DzKjXpHj0qMI5m9VrZuz3wSlI+OEI=
-github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
-github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
-github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
-github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
 github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=
 github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
 github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc=
 github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
-github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-client-go v2.27.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-client-go v2.29.1+incompatible h1:R9ec3zO3sGpzs0abd43Y+fBZRJ9uiH6lXyR/+u6brW4=
 github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
+github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
+github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
 github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
 github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
-github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
-github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
-github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
@@ -868,41 +838,28 @@
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v0.0.0-20170210233622-6b67b3fab74d/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
 github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
 github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
-github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
-gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
-gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
-gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
-gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059 h1:X7+3GQIxUpScXpIMCU5+sfpYvZyBIQ3GMlEosP7Jssw=
-gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
-gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
-gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2 h1:zz/4sD22gZqvAdBgd6gYLRIbvrgoQLDE7emPK0sXC6o=
-gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
+gitlab.com/gitlab-org/gitaly/v15 v15.4.0-rc2 h1:ngV/NWEMz4TRlnJFzBpatdXkEIDMHsU3YUzGdZU63qY=
+gitlab.com/gitlab-org/gitaly/v15 v15.4.0-rc2/go.mod h1:Rv/LoDU5I3cOP6KLUWFjC1eigcay2u8b8ZcsW86A0Xo=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed h1:aXSyBpG6K/QsTGevZnpFoDR7Nwvn24RpkDoWe37B8eY=
 gitlab.com/gitlab-org/golang-crypto v0.0.0-20220616060731-4818747c9fed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM=
-gitlab.com/gitlab-org/labkit v0.0.0-20200908084045-45895e129029/go.mod h1:SNfxkfUwVNECgtmluVayv0GWFgEjjBs5AzgsowPQuo0=
-gitlab.com/gitlab-org/labkit v1.0.0/go.mod h1:nohrYTSLDnZix0ebXZrbZJjymRar8HeV2roWL5/jw2U=
-gitlab.com/gitlab-org/labkit v1.4.1/go.mod h1:x5JO5uvdX4t6e/TZXLXZnFL5AcKz2uLLd3uKXZcuO4k=
-gitlab.com/gitlab-org/labkit v1.5.0/go.mod h1:1ZuVZpjSpCKUgjLx8P6jzkkQFxJI1thUKr6yKV3p0vY=
 gitlab.com/gitlab-org/labkit v1.16.0 h1:Vm3NAMZ8RqAunXlvPWby3GJ2R35vsYGP6Uu0YjyMIlY=
 gitlab.com/gitlab-org/labkit v1.16.0/go.mod h1:bcxc4ZpAC+WyACgyKl7FcvT2XXAbl8CrzN6UY+w8cMc=
-go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
+go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
+go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
-go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
-go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -917,20 +874,24 @@
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
-go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
+go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
 go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
 go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
-go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
-gocloud.dev v0.23.0/go.mod h1:zklCCIIo1N9ELkU2S2E7tW8P8eeMU7oGLeQCXdDwx9Q=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+gocloud.dev v0.26.0/go.mod h1:mkUgejbnbLotorqDyvedJO20XcZNTynmSeVSQS9btVg=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -948,7 +909,6 @@
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -960,7 +920,6 @@
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@@ -973,16 +932,15 @@
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -995,7 +953,6 @@
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -1005,7 +962,6 @@
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -1023,13 +979,20 @@
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
+golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA=
+golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1042,12 +1005,17 @@
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a h1:4Kd8OPUx1xgUwrHDaviWZO8MsgoZTZYC3g+8m16RBww=
 golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM=
+golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1058,16 +1026,15 @@
 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1077,33 +1044,28 @@
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1122,20 +1084,14 @@
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1144,13 +1100,30 @@
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
+golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1161,18 +1134,16 @@
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
-golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
+golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -1193,12 +1164,10 @@
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -1213,6 +1182,7 @@
 golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
@@ -1238,8 +1208,8 @@
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1252,13 +1222,10 @@
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
-google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
@@ -1277,31 +1244,42 @@
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
-google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA=
+google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
 google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
 google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
 google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
 google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
 google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
-google.golang.org/api v0.54.0 h1:ECJUVngj71QI6XEm7b1sAf8BljU5inEhMbKPR8Lxhhk=
 google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
+google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
+google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E=
+google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
+google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
+google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
+google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM=
+google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M=
+google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
+google.golang.org/api v0.68.0/go.mod h1:sOM8pTpwgflXRhz+oC8H2Dr+UcbMqkPPWNJo88Q7TH8=
+google.golang.org/api v0.69.0/go.mod h1:boanBiw+h5c3s+tBPgEzLDRHfFLWV0qXxRHz3ws7C80=
+google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
+google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
+google.golang.org/api v0.74.0 h1:ExR2D+5TYIrMphWgs5JCgwRhEDlPDXXrLwHHMgPHTXE=
+google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
@@ -1341,12 +1319,9 @@
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
-google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210420162539-3c870d7478d2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210517163617-5e0236093d7a/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
@@ -1355,19 +1330,43 @@
 google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
 google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
 google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
-google.golang.org/genproto v0.0.0-20210813162853-db860fec028c h1:iLQakcwWG3k/++1q/46apVb1sUQ3IqIdn9yUE6eh/xA=
 google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
-google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
-google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
+google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220204002441-d6cc3cc0770e/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220216160803-4663080d8bc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
+google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de h1:9Ti5SG2U4cAcluryUo/sFay3TQKoxiFMfaT0pbizU7k=
+google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
@@ -1388,8 +1387,12 @@
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
 google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
-google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
 google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
+google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w=
+google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -1403,10 +1406,10 @@
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-gopkg.in/DataDog/dd-trace-go.v1 v1.7.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
-gopkg.in/DataDog/dd-trace-go.v1 v1.30.0/go.mod h1:SnKViq44dv/0gjl9RpkP0Y2G3BJSRkp6eYdCSu39iI8=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 h1:DkD0plWEVUB8v/Ru6kRBW30Hy/fRNBC8hPdcExuBZMc=
 gopkg.in/DataDog/dd-trace-go.v1 v1.32.0/go.mod h1:wRKMf/tRASHwH/UOfPQ3IQmVFhTz2/1a1/mpXoIjF54=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
@@ -1416,28 +1419,17 @@
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
-gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
-gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
-gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
 gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
 gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
-gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
-gopkg.in/jcmturner/goidentity.v2 v2.0.0/go.mod h1:vCwK9HeXksMeUmQ4SxDd1tRz4LejrKh3KRVjQWhjvZI=
-gopkg.in/jcmturner/gokrb5.v5 v5.3.0/go.mod h1:oQz8Wc5GsctOTgCVyKad1Vw4TCWz5G6gfIQr88RPv4k=
-gopkg.in/jcmturner/rpc.v0 v0.0.2/go.mod h1:NzMq6cRzR9lipgw7WxRBHNx5N8SifBuaCQsOT1kWY/E=
+gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1448,9 +1440,10 @@
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1458,11 +1451,8 @@
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
 nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
-sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
diff --git a/heptapod-ci.yml b/heptapod-ci.yml
--- a/heptapod-ci.yml
+++ b/heptapod-ci.yml
@@ -31,7 +31,7 @@
     - go version
     - which go
   services:
-    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:v14.10.1
+    - name: registry.gitlab.com/gitlab-org/build/cng/gitaly:v15.5.0
       # Disable the hooks so we don't have to stub the GitLab API
       command: ["bash", "-c", "mkdir -p /home/git/repositories && rm -rf /srv/gitlab-shell/hooks/* && exec /usr/bin/env GITALY_TESTING_NO_GIT_HOOKS=1 /scripts/process-wrapper"]
       alias: gitaly
@@ -43,7 +43,7 @@
   image: golang:${GO_VERSION}
   parallel:
     matrix:
-      - GO_VERSION: ["1.17", "1.18"]
+      - GO_VERSION: ["1.17", "1.18", "1.19"]
   after_script:
     - make coverage
   coverage: '/\d+.\d+%/'
diff --git a/internal/command/README.md b/internal/command/README.md
new file mode 100644
--- /dev/null
+++ b/internal/command/README.md
@@ -0,0 +1,30 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Overview
+
+This package consists of a set of packages that are responsible for executing a particular command/feature/operation.
+The full list of features can be viewed [here](https://gitlab.com/gitlab-org/gitlab-shell/-/blob/main/doc/features.md).
+The commands implement the common interface:
+
+```go
+type Command interface {
+	Execute(ctx context.Context) error
+}
+```
+
+A command is executed by running the `Execute` method. The execution logic mostly shares the common pattern:
+
+- Parse the arguments and validate them.
+- Communicate with GitLab Rails using [gitlabnet](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/gitlabnet) package. For example, it can be checking whether a client is authorized to execute this particular command or asking for a personal access token in order to return it to the client.
+- If a command is related to Git operations, establish a connection with Gitaly using [handler](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/handler) and [gitaly](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/gitaly) packages and provide two-way communication between Gitaly and the client.
+- Return results to the client.
+
+This package is being used to build a particular command based on the passed arguments in the following files that are under `cmd` directory:
+- [cmd/gitlab-shell/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/gitlab-shell/command)
+- [cmd/check/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/check/command)
+- [cmd/gitlab-shell-authorized-keys-check/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/gitlab-shell-authorized-keys-check/command)
+- [cmd/gitlab-shell-authorized-principals-check/command](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/cmd/gitlab-shell-authorized-principals-check/command)
diff --git a/internal/command/receivepack/gitalycall.go b/internal/command/receivepack/gitalycall.go
--- a/internal/command/receivepack/gitalycall.go
+++ b/internal/command/receivepack/gitalycall.go
@@ -5,8 +5,8 @@
 
 	"google.golang.org/grpc"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/shared/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
diff --git a/internal/command/uploadarchive/gitalycall.go b/internal/command/uploadarchive/gitalycall.go
--- a/internal/command/uploadarchive/gitalycall.go
+++ b/internal/command/uploadarchive/gitalycall.go
@@ -5,8 +5,8 @@
 
 	"google.golang.org/grpc"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
diff --git a/internal/command/uploadpack/gitalycall.go b/internal/command/uploadpack/gitalycall.go
--- a/internal/command/uploadpack/gitalycall.go
+++ b/internal/command/uploadpack/gitalycall.go
@@ -5,8 +5,8 @@
 
 	"google.golang.org/grpc"
 
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/handler"
diff --git a/internal/gitaly/gitaly.go b/internal/gitaly/gitaly.go
--- a/internal/gitaly/gitaly.go
+++ b/internal/gitaly/gitaly.go
@@ -9,9 +9,9 @@
 	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
 	"google.golang.org/grpc"
 
-	gitalyauth "gitlab.com/gitlab-org/gitaly/v14/auth"
-	"gitlab.com/gitlab-org/gitaly/v14/client"
-	gitalyclient "gitlab.com/gitlab-org/gitaly/v14/client"
+	gitalyauth "gitlab.com/gitlab-org/gitaly/v15/auth"
+	"gitlab.com/gitlab-org/gitaly/v15/client"
+	gitalyclient "gitlab.com/gitlab-org/gitaly/v15/client"
 	"gitlab.com/gitlab-org/labkit/correlation"
 	grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc"
 	"gitlab.com/gitlab-org/labkit/log"
diff --git a/internal/gitlabnet/accessverifier/client.go b/internal/gitlabnet/accessverifier/client.go
--- a/internal/gitlabnet/accessverifier/client.go
+++ b/internal/gitlabnet/accessverifier/client.go
@@ -5,7 +5,7 @@
 	"fmt"
 	"net/http"
 
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/client"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
diff --git a/internal/gitlabnet/accessverifier/client_test.go b/internal/gitlabnet/accessverifier/client_test.go
--- a/internal/gitlabnet/accessverifier/client_test.go
+++ b/internal/gitlabnet/accessverifier/client_test.go
@@ -10,7 +10,7 @@
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/client/testserver"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
diff --git a/internal/handler/exec.go b/internal/handler/exec.go
--- a/internal/handler/exec.go
+++ b/internal/handler/exec.go
@@ -16,7 +16,7 @@
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
 
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/labkit/log"
 )
 
diff --git a/internal/handler/exec_test.go b/internal/handler/exec_test.go
--- a/internal/handler/exec_test.go
+++ b/internal/handler/exec_test.go
@@ -11,7 +11,7 @@
 	"google.golang.org/grpc/metadata"
 	grpcstatus "google.golang.org/grpc/status"
 
-	pb "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+	pb "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/command/commandargs"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
 	"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
# HG changeset patch
# User Ash McKenzie <amckenzie@gitlab.com>
# Date 1673320652 -39600
#      Tue Jan 10 14:17:32 2023 +1100
# Branch heptapod-oldstable
# Node ID a9bf7061f262b69e9b1aef4166629deb84215166
# Parent  5603c54514186b70bfd3a4013d8eace4811cc85a
Fix build for Golang 1.19 by graft from more recent GitLab Shell

Graft of 7a2e02b17485 done by gracinet. The specific problem with
CI is that the `golang:1.19` is now based on Debian 12.

Orginal description:

Explicitly install webrick for Ruby 3.x

diff --git a/Gemfile b/Gemfile
--- a/Gemfile
+++ b/Gemfile
@@ -2,6 +2,7 @@
 
 group :development, :test do
   gem 'rspec', '~> 3.8.0'
+  gem 'webrick', '~> 1.7'
 end
 
 group :development, :danger do
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -104,6 +104,7 @@
     terminal-table (3.0.2)
       unicode-display_width (>= 1.1.1, < 3)
     unicode-display_width (2.2.0)
+    webrick (1.7.0)
 
 PLATFORMS
   ruby
@@ -111,6 +112,7 @@
 DEPENDENCIES
   gitlab-dangerfiles (~> 3.5.1)
   rspec (~> 3.8.0)
+  webrick (~> 1.7)
 
 BUNDLED WITH
    2.3.15
# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1690886693 -7200
#      Tue Aug 01 12:44:53 2023 +0200
# Branch heptapod-oldstable
# Node ID 87760aef72bc44fd60c199df20504b69200e5644
# Parent  a9bf7061f262b69e9b1aef4166629deb84215166
# Parent  d33b6e207eb85d15eeaa7364a79b2d7084644084
heptapod#823: making 0.36 the old stable