diff options
author | Paul Okstad <pokstad@gitlab.com> | 2020-11-17 21:24:52 +0300 |
---|---|---|
committer | Paul Okstad <pokstad@gitlab.com> | 2020-11-17 21:24:52 +0300 |
commit | 3464eea3b9c7c4cb195c1bb9714291c94524d2f5 (patch) | |
tree | 839166a61e8f8896af61068291d63a0f7066ad24 | |
parent | 381df30b1b49b9fcfbc1e4107a106a70f1403c7d (diff) | |
parent | 13839ed64f05c62cfe486424a8cd14279b51ae19 (diff) |
Merge branch 'po-client-certs-internal-gitlab-api' into 'master'
Configure the GitLab internal API client to provide client certificates when using TLS
Closes #3160
See merge request gitlab-org/gitaly!2794
-rw-r--r-- | changelogs/unreleased/po-client-certs-internal-gitlab-api.yml | 6 | ||||
-rw-r--r-- | cmd/gitaly-hooks/hooks.go | 2 | ||||
-rw-r--r-- | cmd/gitaly-hooks/hooks_test.go | 6 | ||||
-rw-r--r-- | cmd/gitaly-lfs-smudge/lfs_smudge.go | 29 | ||||
-rw-r--r-- | cmd/gitaly-lfs-smudge/lfs_smudge_test.go | 35 | ||||
-rw-r--r-- | cmd/gitaly/main.go | 2 | ||||
-rw-r--r-- | go.mod | 2 | ||||
-rw-r--r-- | go.sum | 4 | ||||
-rw-r--r-- | internal/gitaly/hook/access.go | 17 | ||||
-rw-r--r-- | internal/gitaly/hook/access_test.go | 18 | ||||
-rw-r--r-- | internal/gitaly/hook/testdata/certs/server.crt | 34 | ||||
-rw-r--r-- | internal/gitaly/hook/testdata/certs/server.key | 52 | ||||
-rw-r--r-- | internal/gitaly/service/hook/post_receive_test.go | 4 | ||||
-rw-r--r-- | internal/gitaly/service/hook/pre_receive_test.go | 8 | ||||
-rw-r--r-- | internal/gitaly/service/repository/archive.go | 8 | ||||
-rw-r--r-- | internal/gitaly/service/repository/archive_test.go | 4 | ||||
-rw-r--r-- | internal/testhelper/testserver.go | 44 |
17 files changed, 236 insertions, 39 deletions
diff --git a/changelogs/unreleased/po-client-certs-internal-gitlab-api.yml b/changelogs/unreleased/po-client-certs-internal-gitlab-api.yml new file mode 100644 index 000000000..7c6051639 --- /dev/null +++ b/changelogs/unreleased/po-client-certs-internal-gitlab-api.yml @@ -0,0 +1,6 @@ +--- +title: Configure the GitLab internal API client to provide client certificates when + using TLS +merge_request: 2794 +author: +type: security diff --git a/cmd/gitaly-hooks/hooks.go b/cmd/gitaly-hooks/hooks.go index c4cbf3bef..80b000c90 100644 --- a/cmd/gitaly-hooks/hooks.go +++ b/cmd/gitaly-hooks/hooks.go @@ -331,7 +331,7 @@ func check(configPath string) (*hook.CheckInfo, error) { return nil, err } - gitlabAPI, err := hook.NewGitlabAPI(config.Config.Gitlab) + gitlabAPI, err := hook.NewGitlabAPI(config.Config.Gitlab, config.Config.TLS) if err != nil { return nil, err } diff --git a/cmd/gitaly-hooks/hooks_test.go b/cmd/gitaly-hooks/hooks_test.go index 7c9d50bb9..bb81ec32a 100644 --- a/cmd/gitaly-hooks/hooks_test.go +++ b/cmd/gitaly-hooks/hooks_test.go @@ -156,7 +156,7 @@ func testHooksPrePostReceive(t *testing.T) { config.Config.Gitlab.HTTPSettings.User = gitlabUser config.Config.Gitlab.HTTPSettings.Password = gitlabPassword - gitlabAPI, err := gitalyhook.NewGitlabAPI(config.Config.Gitlab) + gitlabAPI, err := gitalyhook.NewGitlabAPI(config.Config.Gitlab, config.Config.TLS) require.NoError(t, err) socket, stop := runHookServiceServerWithAPI(t, token, gitlabAPI) @@ -362,7 +362,7 @@ func TestHooksPostReceiveFailed(t *testing.T) { token := "abc123" - gitlabAPI, err := gitalyhook.NewGitlabAPI(config.Config.Gitlab) + gitlabAPI, err := gitalyhook.NewGitlabAPI(config.Config.Gitlab, config.Config.TLS) require.NoError(t, err) socket, stop := runHookServiceServerWithAPI(t, token, gitlabAPI) @@ -488,7 +488,7 @@ func TestHooksNotAllowed(t *testing.T) { token := "abc123" - gitlabAPI, err := gitalyhook.NewGitlabAPI(config.Config.Gitlab) + gitlabAPI, err := gitalyhook.NewGitlabAPI(config.Config.Gitlab, config.Config.TLS) require.NoError(t, err) socket, stop := runHookServiceServerWithAPI(t, token, gitlabAPI) diff --git a/cmd/gitaly-lfs-smudge/lfs_smudge.go b/cmd/gitaly-lfs-smudge/lfs_smudge.go index d08716404..07b63ca23 100644 --- a/cmd/gitaly-lfs-smudge/lfs_smudge.go +++ b/cmd/gitaly-lfs-smudge/lfs_smudge.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "errors" "fmt" "io" "path/filepath" @@ -67,14 +68,16 @@ func handleSmudge(to io.Writer, from io.Reader, config configProvider) (io.Reade logger.WithField("oid", ptr.Oid).Debug("decoded LFS OID") - cfg, glRepository, err := loadConfig(config) + glCfg, tlsCfg, glRepository, err := loadConfig(config) if err != nil { return contents, err } - logger.WithField("gitlab_config", cfg).Debug("loaded GitLab API config") + logger.WithField("gitlab_config", glCfg). + WithField("gitaly_tls_config", tlsCfg). + Debug("loaded GitLab API config") - client, err := hook.NewGitlabNetClient(cfg) + client, err := hook.NewGitlabNetClient(glCfg, tlsCfg) if err != nil { return contents, err } @@ -92,22 +95,32 @@ func handleSmudge(to io.Writer, from io.Reader, config configProvider) (io.Reade return contents, nil } -func loadConfig(cfgProvider configProvider) (config.Gitlab, string, error) { +func loadConfig(cfgProvider configProvider) (config.Gitlab, config.TLS, string, error) { var cfg config.Gitlab + var tlsCfg config.TLS glRepository := cfgProvider.Get("GL_REPOSITORY") if glRepository == "" { - return cfg, "", fmt.Errorf("error loading project: GL_REPOSITORY is not defined") + return cfg, tlsCfg, "", fmt.Errorf("error loading project: GL_REPOSITORY is not defined") } u := cfgProvider.Get("GL_INTERNAL_CONFIG") if u == "" { - return cfg, glRepository, fmt.Errorf("unable to retrieve GL_INTERNAL_CONFIG") + return cfg, tlsCfg, glRepository, fmt.Errorf("unable to retrieve GL_INTERNAL_CONFIG") } if err := json.Unmarshal([]byte(u), &cfg); err != nil { - return cfg, glRepository, fmt.Errorf("unable to unmarshal GL_INTERNAL_CONFIG: %v", err) + return cfg, tlsCfg, glRepository, fmt.Errorf("unable to unmarshal GL_INTERNAL_CONFIG: %v", err) } - return cfg, glRepository, nil + u = cfgProvider.Get("GITALY_TLS") + if u == "" { + return cfg, tlsCfg, glRepository, errors.New("unable to retrieve GITALY_TLS") + } + + if err := json.Unmarshal([]byte(u), &tlsCfg); err != nil { + return cfg, tlsCfg, glRepository, fmt.Errorf("unable to unmarshal GITALY_TLS: %w", err) + } + + return cfg, tlsCfg, glRepository, nil } diff --git a/cmd/gitaly-lfs-smudge/lfs_smudge_test.go b/cmd/gitaly-lfs-smudge/lfs_smudge_test.go index 8ebd8afb6..244cd16d5 100644 --- a/cmd/gitaly-lfs-smudge/lfs_smudge_test.go +++ b/cmd/gitaly-lfs-smudge/lfs_smudge_test.go @@ -24,14 +24,19 @@ size 177735 glRepository = "project-1" secretToken = "topsecret" testData = "hello world" + certPath = "../../internal/gitaly/hook/testdata/certs/server.crt" + keyPath = "../../internal/gitaly/hook/testdata/certs/server.key" ) var ( defaultOptions = testhelper.GitlabTestServerOptions{ - SecretToken: secretToken, - LfsBody: testData, - LfsOid: lfsOid, - GlRepository: glRepository, + SecretToken: secretToken, + LfsBody: testData, + LfsOid: lfsOid, + GlRepository: glRepository, + ClientCACertPath: certPath, + ServerCertPath: certPath, + ServerKeyPath: keyPath, } ) @@ -62,7 +67,7 @@ func runTestServer(t *testing.T, options testhelper.GitlabTestServerOptions) (co serverURL, serverCleanup := testhelper.NewGitlabTestServer(t, options) - c := config.Gitlab{URL: serverURL, SecretFile: secretFilePath} + c := config.Gitlab{URL: serverURL, SecretFile: secretFilePath, HTTPSettings: config.HTTPSettings{CAFile: certPath}} return c, func() { cleanup() @@ -80,6 +85,12 @@ func TestSuccessfulLfsSmudge(t *testing.T) { cfg, err := json.Marshal(c) require.NoError(t, err) + tlsCfg, err := json.Marshal(config.TLS{ + CertPath: certPath, + KeyPath: keyPath, + }) + require.NoError(t, err) + tmpDir, err := ioutil.TempDir("", "") require.NoError(t, err) defer os.RemoveAll(tmpDir) @@ -88,6 +99,7 @@ func TestSuccessfulLfsSmudge(t *testing.T) { "GL_REPOSITORY": "project-1", "GL_INTERNAL_CONFIG": string(cfg), "GITALY_LOG_DIR": tmpDir, + "GITALY_TLS": string(tlsCfg), } cfgProvider := &mapConfig{env: env} initLogging(cfgProvider) @@ -113,9 +125,11 @@ func TestUnsuccessfulLfsSmudge(t *testing.T) { desc string data string missingEnv string + tlsCfg config.TLS expectedError bool options testhelper.GitlabTestServerOptions expectedLogMessage string + expectedGitalyTLS string }{ { desc: "bad LFS pointer", @@ -152,6 +166,13 @@ func TestUnsuccessfulLfsSmudge(t *testing.T) { expectedError: true, expectedLogMessage: "error loading LFS object", }, + { + desc: "invalid TLS paths", + data: lfsPointer, + options: defaultOptions, + tlsCfg: config.TLS{CertPath: "fake-path", KeyPath: "not-real"}, + expectedError: true, + }, } for _, tc := range testCases { @@ -162,6 +183,9 @@ func TestUnsuccessfulLfsSmudge(t *testing.T) { cfg, err := json.Marshal(c) require.NoError(t, err) + tlsCfg, err := json.Marshal(tc.tlsCfg) + require.NoError(t, err) + tmpDir, err := ioutil.TempDir("", "") require.NoError(t, err) defer os.RemoveAll(tmpDir) @@ -170,6 +194,7 @@ func TestUnsuccessfulLfsSmudge(t *testing.T) { "GL_REPOSITORY": "project-1", "GL_INTERNAL_CONFIG": string(cfg), "GITALY_LOG_DIR": tmpDir, + "GITALY_TLS": string(tlsCfg), } if tc.missingEnv != "" { diff --git a/cmd/gitaly/main.go b/cmd/gitaly/main.go index 3e4423f63..b47f04658 100644 --- a/cmd/gitaly/main.go +++ b/cmd/gitaly/main.go @@ -113,7 +113,7 @@ func run(b *bootstrap.Bootstrap) error { if config.SkipHooks() { log.Warn("skipping GitLab API client creation since hooks are bypassed via GITALY_TESTING_NO_GIT_HOOKS") } else { - gitlabAPI, err = hook.NewGitlabAPI(config.Config.Gitlab) + gitlabAPI, err = hook.NewGitlabAPI(config.Config.Gitlab, config.Config.TLS) if err != nil { log.Fatalf("could not create GitLab API client: %v", err) } @@ -25,7 +25,7 @@ require ( github.com/sirupsen/logrus v1.7.0 github.com/stretchr/testify v1.6.1 github.com/uber/jaeger-client-go v2.15.0+incompatible - gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201106143703-da924afd346d + gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc gitlab.com/gitlab-org/labkit v0.0.0-20201113080642-40dcf811328c go.uber.org/atomic v1.4.0 // indirect golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect @@ -421,8 +421,8 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg= -gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201106143703-da924afd346d h1:C5BHPkyOWx6mDFz2LA3O/3o2ixSGsQBnaLSSQV7KOj8= -gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201106143703-da924afd346d/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U= +gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc h1:ENBJqb2gK3cZFHe8dbEM/TPXCwkAxDuxGK255ux4XPg= +gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U= gitlab.com/gitlab-org/labkit v0.0.0-20190221122536-0c3fc7cdd57c/go.mod h1:rYhLgfrbEcyfinG+R3EvKu6bZSsmwQqcXzLfHWSfUKM= 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 h1:L7b9YLsU3zBfTShAPl4fjhgFdfSvuo9tu4VobJdcKDs= diff --git a/internal/gitaly/hook/access.go b/internal/gitaly/hook/access.go index 152e8c0e8..4cb326998 100644 --- a/internal/gitaly/hook/access.go +++ b/internal/gitaly/hook/access.go @@ -71,20 +71,29 @@ type gitlabAPI struct { } // NewGitlabNetClient creates an HTTP client to talk to the Rails internal API -func NewGitlabNetClient(gitlabCfg config.Gitlab) (*client.GitlabNetClient, error) { +func NewGitlabNetClient(gitlabCfg config.Gitlab, tlsCfg config.TLS) (*client.GitlabNetClient, error) { url, err := url.PathUnescape(gitlabCfg.URL) if err != nil { return nil, err } - httpClient := client.NewHTTPClient( + var opts []client.HTTPClientOpt + if tlsCfg.CertPath != "" && tlsCfg.KeyPath != "" { + opts = append(opts, client.WithClientCert(tlsCfg.CertPath, tlsCfg.KeyPath)) + } + + httpClient, err := client.NewHTTPClientWithOpts( url, gitlabCfg.RelativeURLRoot, gitlabCfg.HTTPSettings.CAFile, gitlabCfg.HTTPSettings.CAPath, gitlabCfg.HTTPSettings.SelfSigned, uint64(gitlabCfg.HTTPSettings.ReadTimeout), + opts, ) + if err != nil { + return nil, fmt.Errorf("building new HTTP client for GitLab API: %w", err) + } if httpClient == nil { return nil, fmt.Errorf("%s is not a valid url", gitlabCfg.URL) @@ -106,8 +115,8 @@ func NewGitlabNetClient(gitlabCfg config.Gitlab) (*client.GitlabNetClient, error } // NewGitlabAPI creates a GitLabAPI to talk to the Rails internal API -func NewGitlabAPI(gitlabCfg config.Gitlab) (GitlabAPI, error) { - client, err := NewGitlabNetClient(gitlabCfg) +func NewGitlabAPI(gitlabCfg config.Gitlab, tlsCfg config.TLS) (GitlabAPI, error) { + client, err := NewGitlabNetClient(gitlabCfg, tlsCfg) if err != nil { return nil, err } diff --git a/internal/gitaly/hook/access_test.go b/internal/gitaly/hook/access_test.go index deb17bfcb..fab76522d 100644 --- a/internal/gitaly/hook/access_test.go +++ b/internal/gitaly/hook/access_test.go @@ -23,6 +23,9 @@ type postReceiveRequest struct { PushOptions []string `json:"push_options,omitempty"` } +// TestAllowedVerifyParams uses client cert fixtures to test TLS connections. To +// regenerate these certs, run `go generate access_test.go`. +//go:generate openssl req -newkey rsa:4096 -new -nodes -x509 -days 3650 -out testdata/certs/server.crt -keyout testdata/certs/server.key -subj "/C=US/ST=California/L=San Francisco/O=GitLab/OU=GitLab-Shell/CN=localhost" -addext "subjectAltName = IP:127.0.0.1" func TestAllowedVerifyParams(t *testing.T) { user, password := "user", "password" secretToken := "topsecret" @@ -62,6 +65,9 @@ func TestAllowedVerifyParams(t *testing.T) { GitObjectDir: gitObjectDirFull, GitAlternateObjectDirs: gitAlternateObjectDirsFull, RepoPath: testRepoPath, + ClientCACertPath: "testdata/certs/server.crt", + ServerCertPath: "testdata/certs/server.crt", + ServerKeyPath: "testdata/certs/server.key", }) defer cleanup() @@ -71,7 +77,11 @@ func TestAllowedVerifyParams(t *testing.T) { HTTPSettings: config.HTTPSettings{ User: user, Password: password, + CAFile: "testdata/certs/server.crt", }, + }, config.TLS{ + CertPath: "testdata/certs/server.crt", + KeyPath: "testdata/certs/server.key", }) require.NoError(t, err) @@ -198,7 +208,7 @@ func TestEscapedAndRelativeURLs(t *testing.T) { User: user, Password: password, }, - }) + }, config.TLS{}) require.NoError(t, err) allowed, _, err := c.Allowed(context.Background(), testRepo, glRepository, glID, protocol, changes) require.NoError(t, err) @@ -330,7 +340,7 @@ func TestAllowedResponseHandling(t *testing.T) { c, err := hook.NewGitlabAPI(config.Gitlab{ URL: server.URL, SecretFile: secretFilePath, - }) + }, config.TLS{}) require.NoError(t, err) allowed, message, err := c.Allowed(context.Background(), testRepo, "repo-1", "key-123", "http", "a\nb\nc\nd") @@ -418,7 +428,7 @@ func TestPrereceive(t *testing.T) { c, err := hook.NewGitlabAPI(config.Gitlab{ URL: server.URL, SecretFile: secretFilePath, - }) + }, config.TLS{}) require.NoError(t, err) success, err := c.PreReceive(context.Background(), "key-123") @@ -494,7 +504,7 @@ func TestPostReceive(t *testing.T) { c, err := hook.NewGitlabAPI(config.Gitlab{ URL: server.URL, SecretFile: secretFilePath, - }) + }, config.TLS{}) require.NoError(t, err) repositoryID := "project-123" diff --git a/internal/gitaly/hook/testdata/certs/server.crt b/internal/gitaly/hook/testdata/certs/server.crt new file mode 100644 index 000000000..af0103e8e --- /dev/null +++ b/internal/gitaly/hook/testdata/certs/server.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +bjogyV1O8cL8dTQLHxeQG9oC +-----END CERTIFICATE----- diff --git a/internal/gitaly/hook/testdata/certs/server.key b/internal/gitaly/hook/testdata/certs/server.key new file mode 100644 index 000000000..f343b2be7 --- /dev/null +++ b/internal/gitaly/hook/testdata/certs/server.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +QY02aq2BcpobB0bNeQ8o8RipNcKKF9U= +-----END PRIVATE KEY----- diff --git a/internal/gitaly/service/hook/post_receive_test.go b/internal/gitaly/service/hook/post_receive_test.go index bd09c2d00..726798e5c 100644 --- a/internal/gitaly/service/hook/post_receive_test.go +++ b/internal/gitaly/service/hook/post_receive_test.go @@ -90,7 +90,7 @@ func TestHooksMissingStdin(t *testing.T) { config.Config.Gitlab = gitlabConfig - api, err := gitalyhook.NewGitlabAPI(gitlabConfig) + api, err := gitalyhook.NewGitlabAPI(gitlabConfig, config.Config.TLS) require.NoError(t, err) testCases := []struct { @@ -230,7 +230,7 @@ To create a merge request for okay, visit: config.Config.Gitlab = gitlabConfig - api, err := gitalyhook.NewGitlabAPI(gitlabConfig) + api, err := gitalyhook.NewGitlabAPI(gitlabConfig, config.Config.TLS) require.NoError(t, err) serverSocketPath, stop := runHooksServerWithAPI(t, api, config.Config) diff --git a/internal/gitaly/service/hook/pre_receive_test.go b/internal/gitaly/service/hook/pre_receive_test.go index 2e706a55a..b97e7e6c6 100644 --- a/internal/gitaly/service/hook/pre_receive_test.go +++ b/internal/gitaly/service/hook/pre_receive_test.go @@ -132,7 +132,7 @@ func TestPreReceiveHook_GitlabAPIAccess(t *testing.T) { SecretFile: secretFilePath, } - gitlabAPI, err := gitalyhook.NewGitlabAPI(gitlabConfig) + gitlabAPI, err := gitalyhook.NewGitlabAPI(gitlabConfig, config.Config.TLS) require.NoError(t, err) serverSocketPath, stop := runHooksServerWithAPI(t, gitlabAPI, config.Cfg{}) @@ -235,7 +235,7 @@ func TestPreReceive_APIErrors(t *testing.T) { SecretFile: secretFilePath, } - gitlabAPI, err := gitalyhook.NewGitlabAPI(gitlabConfig) + gitlabAPI, err := gitalyhook.NewGitlabAPI(gitlabConfig, config.Config.TLS) require.NoError(t, err) serverSocketPath, stop := runHooksServerWithAPI(t, gitlabAPI, config.Cfg{}) @@ -295,7 +295,7 @@ exit %d SecretFile: secretFilePath, } - gitlabAPI, err := gitalyhook.NewGitlabAPI(gitlabConfig) + gitlabAPI, err := gitalyhook.NewGitlabAPI(gitlabConfig, config.Config.TLS) require.NoError(t, err) serverSocketPath, stop := runHooksServerWithAPI(t, gitlabAPI, config.Cfg{}) @@ -413,7 +413,7 @@ func TestPreReceiveHook_Primary(t *testing.T) { gitlabAPI, err := gitalyhook.NewGitlabAPI(config.Gitlab{ URL: srv.URL, SecretFile: secretFilePath, - }) + }, config.Config.TLS) require.NoError(t, err) serverSocketPath, stop := runHooksServerWithAPI(t, gitlabAPI, config.Cfg{}) diff --git a/internal/gitaly/service/repository/archive.go b/internal/gitaly/service/repository/archive.go index bce628c89..49614c73d 100644 --- a/internal/gitaly/service/repository/archive.go +++ b/internal/gitaly/service/repository/archive.go @@ -31,6 +31,7 @@ type archiveParams struct { archivePath string exclude []string internalCfg []byte + tlsCfg []byte binDir string loggingDir string } @@ -86,6 +87,11 @@ func (s *server) GetArchive(in *gitalypb.GetArchiveRequest, stream gitalypb.Repo return err } + tlsCfg, err := json.Marshal(s.cfg.TLS) + if err != nil { + return err + } + return handleArchive(archiveParams{ ctx: ctx, writer: writer, @@ -95,6 +101,7 @@ func (s *server) GetArchive(in *gitalypb.GetArchiveRequest, stream gitalypb.Repo archivePath: path, exclude: exclude, internalCfg: gitlabConfig, + tlsCfg: tlsCfg, binDir: s.binDir, loggingDir: s.loggingCfg.Dir, }) @@ -188,6 +195,7 @@ func handleArchive(p archiveParams) error { fmt.Sprintf("GL_REPOSITORY=%s", p.in.GetRepository().GetGlRepository()), fmt.Sprintf("GL_PROJECT_PATH=%s", p.in.GetRepository().GetGlProjectPath()), fmt.Sprintf("GL_INTERNAL_CONFIG=%s", p.internalCfg), + fmt.Sprintf("GITALY_TLS=%s", p.tlsCfg), fmt.Sprintf("CORRELATION_ID=%s", correlation.ExtractFromContext(p.ctx)), fmt.Sprintf("%s=%s", log.GitalyLogDirEnvKey, p.loggingDir), } diff --git a/internal/gitaly/service/repository/archive_test.go b/internal/gitaly/service/repository/archive_test.go index 26b872e38..1188d3344 100644 --- a/internal/gitaly/service/repository/archive_test.go +++ b/internal/gitaly/service/repository/archive_test.go @@ -531,6 +531,9 @@ env | grep -E "^GL_|CORRELATION|GITALY_"`)) cfgData, err := json.Marshal(config.Config.Gitlab) require.NoError(t, err) + tlsCfgData, err := json.Marshal(config.Config.TLS) + require.NoError(t, err) + stream, err := client.GetArchive(ctx, req) require.NoError(t, err) @@ -539,6 +542,7 @@ env | grep -E "^GL_|CORRELATION|GITALY_"`)) require.Contains(t, string(data), "GL_REPOSITORY="+testhelper.GlRepository) require.Contains(t, string(data), "GL_PROJECT_PATH="+testhelper.GlProjectPath) require.Contains(t, string(data), "GL_INTERNAL_CONFIG="+string(cfgData)) + require.Contains(t, string(data), "GITALY_TLS="+string(tlsCfgData)) require.Contains(t, string(data), "CORRELATION_ID="+correlationID) require.Contains(t, string(data), "GITALY_LOG_DIR="+config.Config.Logging.Dir) } diff --git a/internal/testhelper/testserver.go b/internal/testhelper/testserver.go index b6dab7ed9..b20415bc8 100644 --- a/internal/testhelper/testserver.go +++ b/internal/testhelper/testserver.go @@ -2,6 +2,8 @@ package testhelper import ( "context" + "crypto/tls" + "crypto/x509" "encoding/base64" "encoding/json" "errors" @@ -798,6 +800,9 @@ type GitlabTestServerOptions struct { RepoPath string RelativeURLRoot string GlRepository string + ClientCACertPath string // used to verify client certs are valid + ServerCertPath string + ServerKeyPath string } // NewGitlabTestServer returns a mock gitlab server that responds to the hook api endpoints @@ -810,15 +815,45 @@ func NewGitlabTestServer(t FatalLogger, options GitlabTestServerOptions) (url st mux.Handle(prefix+"/check", http.HandlerFunc(handleCheck(options))) mux.Handle(prefix+"/lfs", http.HandlerFunc(handleLfs(options))) + var tlsCfg *tls.Config + if options.ClientCACertPath != "" { + caCertPEM, err := ioutil.ReadFile(options.ClientCACertPath) + if err != nil { + t.Fatalf("reading client CA file: %v", err) + } + + certPool := x509.NewCertPool() + if !certPool.AppendCertsFromPEM(caCertPEM) { + t.Fatalf("unable to add client CA cert to pool") + } + + serverCert, err := tls.LoadX509KeyPair(options.ServerCertPath, options.ServerKeyPath) + if err != nil { + t.Fatalf("unable to load x509 key pair: %v", err) + } + tlsCfg = &tls.Config{ + ClientCAs: certPool, + ClientAuth: tls.RequireAndVerifyClientCert, + Certificates: []tls.Certificate{serverCert}, + } + } + if options.UnixSocket { - return startSocketHTTPServer(t, mux) + return startSocketHTTPServer(t, mux, tlsCfg) } else { - server := httptest.NewServer(mux) + var server *httptest.Server + if tlsCfg == nil { + server = httptest.NewServer(mux) + } else { + server = httptest.NewUnstartedServer(mux) + server.TLS = tlsCfg + server.StartTLS() + } return server.URL, server.Close } } -func startSocketHTTPServer(t FatalLogger, mux *http.ServeMux) (string, func()) { +func startSocketHTTPServer(t FatalLogger, mux *http.ServeMux, tlsCfg *tls.Config) (string, func()) { tmpFile, err := ioutil.TempFile(os.TempDir(), "http-test-server") if err != nil { t.Fatalf("Cannot create temporary file", err) @@ -833,7 +868,8 @@ func startSocketHTTPServer(t FatalLogger, mux *http.ServeMux) (string, func()) { } server := http.Server{ - Handler: mux, + Handler: mux, + TLSConfig: tlsCfg, } go server.Serve(socketListener) |