From ae2066c265d4e927351bf58693722dfe1acebf5f Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Fri, 19 Oct 2018 15:09:15 +0200 Subject: [PATCH 1/7] Add tls configuration to gitaly golang server --- client/dial.go | 29 +++++++++++++++++++++++++---- cmd/gitaly/main.go | 31 +++++++++++++++++++++++++------ config.toml.example | 5 +++++ internal/config/config.go | 8 ++++++++ internal/server/server.go | 22 ++++++++++++++++++++-- 5 files changed, 83 insertions(+), 12 deletions(-) diff --git a/client/dial.go b/client/dial.go index 89f2a10b0c1..9bef70abca0 100644 --- a/client/dial.go +++ b/client/dial.go @@ -1,7 +1,11 @@ package client import ( + "crypto/x509" "fmt" + + "google.golang.org/grpc/credentials" + "net" "net/url" "strings" @@ -11,9 +15,7 @@ import ( ) // DefaultDialOpts hold the default DialOptions for connection to Gitaly over UNIX-socket -var DefaultDialOpts = []grpc.DialOption{ - grpc.WithInsecure(), -} +var DefaultDialOpts = []grpc.DialOption{} // Dial gitaly func Dial(rawAddress string, connOpts []grpc.DialOption) (*grpc.ClientConn, error) { @@ -21,11 +23,25 @@ func Dial(rawAddress string, connOpts []grpc.DialOption) (*grpc.ClientConn, erro if err != nil { return nil, err } + tls := isTls(rawAddress) connOpts = append(connOpts, grpc.WithDialer(func(a string, timeout time.Duration) (net.Conn, error) { return net.DialTimeout(network, a, timeout) })) + + if tls { + certPool, err := x509.SystemCertPool() + if err != nil { + return nil, err + } + + creds := credentials.NewClientTLSFromCert(certPool, "") + connOpts = append(connOpts, grpc.WithTransportCredentials(creds)) + } else { + connOpts = append(connOpts, grpc.WithInsecure()) + } + conn, err := grpc.Dial(addr, connOpts...) if err != nil { return nil, err @@ -34,6 +50,11 @@ func Dial(rawAddress string, connOpts []grpc.DialOption) (*grpc.ClientConn, erro return conn, nil } +func isTls(rawAddress string) bool { + u, _ := url.Parse(rawAddress) + return u.Scheme == "tls" +} + func parseAddress(rawAddress string) (network, addr string, err error) { // Parsing unix:// URL's with url.Parse does not give the result we want // so we do it manually. @@ -48,7 +69,7 @@ func parseAddress(rawAddress string) (network, addr string, err error) { return "", "", err } - if u.Scheme != "tcp" { + if u.Scheme != "tcp" && u.Scheme != "tls" { return "", "", fmt.Errorf("unknown scheme: %q", rawAddress) } if u.Host == "" { diff --git a/cmd/gitaly/main.go b/cmd/gitaly/main.go index 9bacea720f4..f52f5066157 100644 --- a/cmd/gitaly/main.go +++ b/cmd/gitaly/main.go @@ -107,6 +107,7 @@ func main() { tempdir.StartCleaning() var listeners []net.Listener + var secureListeners []net.Listener if socketPath := config.Config.SocketPath; socketPath != "" { l, err := createUnixListener(socketPath) @@ -127,6 +128,15 @@ func main() { listeners = append(listeners, connectioncounter.New("tcp", l)) } + if addr := config.Config.TLS.ListenAddr; addr != "" { + tlsListener, err := net.Listen("tcp", addr) + if err != nil { + log.WithError(err).Fatal("configure tls listener") + } + + secureListeners = append(secureListeners, connectioncounter.New("tls", tlsListener)) + } + if config.Config.PrometheusListenAddr != "" { log.WithField("address", config.Config.PrometheusListenAddr).Info("Starting prometheus listener") promMux := http.NewServeMux() @@ -139,7 +149,7 @@ func main() { }() } - log.WithError(run(listeners)).Fatal("shutting down") + log.WithError(run(listeners, secureListeners)).Fatal("shutting down") } func createUnixListener(socketPath string) (net.Listener, error) { @@ -152,7 +162,7 @@ func createUnixListener(socketPath string) (net.Listener, error) { // Inside here we can use deferred functions. This is needed because // log.Fatal bypasses deferred functions. -func run(listeners []net.Listener) error { +func run(listeners, secureListeners []net.Listener) error { signals := []os.Signal{syscall.SIGTERM, syscall.SIGINT} termCh := make(chan os.Signal, len(signals)) signal.Notify(termCh, signals...) @@ -163,18 +173,27 @@ func run(listeners []net.Listener) error { } defer ruby.Stop() - server := server.New(ruby) - defer server.Stop() + insecureServer := server.New(ruby, false) + defer insecureServer.Stop() - serverErrors := make(chan error, len(listeners)) + serverErrors := make(chan error, len(listeners)+len(secureListeners)) for _, listener := range listeners { // Must pass the listener as a function argument because there is a race // between 'go' and 'for'. go func(l net.Listener) { - serverErrors <- server.Serve(l) + serverErrors <- insecureServer.Serve(l) }(listener) } + if len(secureListeners) > 0 { + secureServer := server.New(ruby, true) + for _, listener := range secureListeners { + go func(l net.Listener) { + serverErrors <- secureServer.Serve(l) + }(listener) + } + } + select { case s := <-termCh: err = fmt.Errorf("received signal %q", s) diff --git a/config.toml.example b/config.toml.example index 2818531d57c..0a354c1f1b1 100644 --- a/config.toml.example +++ b/config.toml.example @@ -16,6 +16,11 @@ bin_dir = "/home/git/gitaly" # token = 'abc123secret' # transitioning = false # Set `transitioning` to true to temporarily allow unauthenticated while rolling out authentication. +# [tls] +# listen_addr = 'localhost:8888' +# certificate_path = '/home/git/cert.cert' +# key_path = '/home/git/key.pem' + # # Git executable settings # [git] # bin_path = "/usr/bin/git" diff --git a/internal/config/config.go b/internal/config/config.go index eb796acd386..d084114350b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -28,11 +28,19 @@ type config struct { Logging Logging `toml:"logging" envconfig:"logging"` Prometheus Prometheus `toml:"prometheus"` Auth Auth `toml:"auth"` + TLS TLS `toml:"tls"` Ruby Ruby `toml:"gitaly-ruby"` GitlabShell GitlabShell `toml:"gitlab-shell"` Concurrency []Concurrency `toml:"concurrency"` } +// TLS configuration +type TLS struct { + ListenAddr string `toml:"listen_addr" split_words:"true"` + CertPath string `toml:"certificate_path"` + KeyPath string `toml:"key_path"` +} + // GitlabShell contains the settings required for executing `gitlab-shell` type GitlabShell struct { Dir string `toml:"dir"` diff --git a/internal/server/server.go b/internal/server/server.go index 3abcceaeb82..a3583c781db 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1,11 +1,14 @@ package server import ( + "crypto/tls" + "github.com/grpc-ecosystem/go-grpc-middleware" "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus" "github.com/grpc-ecosystem/go-grpc-middleware/tags" "github.com/grpc-ecosystem/go-grpc-prometheus" log "github.com/sirupsen/logrus" + "gitlab.com/gitlab-org/gitaly/internal/config" "gitlab.com/gitlab-org/gitaly/internal/helper/fieldextractors" gitalylog "gitlab.com/gitlab-org/gitaly/internal/log" "gitlab.com/gitlab-org/gitaly/internal/logsanitizer" @@ -19,6 +22,7 @@ import ( "gitlab.com/gitlab-org/gitaly/internal/service" "golang.org/x/net/context" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" ) @@ -58,14 +62,14 @@ func init() { } // New returns a GRPC server with all Gitaly services and interceptors set up. -func New(rubyServer *rubyserver.Server) *grpc.Server { +func New(rubyServer *rubyserver.Server, secure bool) *grpc.Server { ctxTagOpts := []grpc_ctxtags.Option{ grpc_ctxtags.WithFieldExtractorForInitialReq(fieldextractors.FieldExtractor), } lh := limithandler.New(concurrencyKeyFn) - server := grpc.NewServer( + opts := []grpc.ServerOption{ grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( grpc_ctxtags.StreamServerInterceptor(ctxTagOpts...), metadatahandler.StreamInterceptor, @@ -92,6 +96,20 @@ func New(rubyServer *rubyserver.Server) *grpc.Server { // converted to errors and logged panichandler.UnaryPanicHandler, )), + } + + // If tls config is specified attempt to extract tls options and use it + // as a grpc.ServerOption + if secure { + cert, err := tls.LoadX509KeyPair(config.Config.TLS.CertPath, config.Config.TLS.KeyPath) + if err != nil { + log.Fatal(err) + } + opts = append(opts, grpc.Creds(credentials.NewServerTLSFromCert(&cert))) + } + + server := grpc.NewServer( + opts..., ) service.RegisterAll(server, rubyServer) -- GitLab From 5c5139e5bdd6dc439db1611311cba8569cfa3f1e Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Wed, 31 Oct 2018 16:18:10 +0200 Subject: [PATCH 2/7] Add changelog entry --- changelogs/unreleased/gitaly-tls.yml | 5 +++++ client/dial.go | 4 ++-- cmd/gitaly/main.go | 5 +++-- internal/server/server.go | 15 +++++++++++++-- 4 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 changelogs/unreleased/gitaly-tls.yml diff --git a/changelogs/unreleased/gitaly-tls.yml b/changelogs/unreleased/gitaly-tls.yml new file mode 100644 index 00000000000..1ce663bc314 --- /dev/null +++ b/changelogs/unreleased/gitaly-tls.yml @@ -0,0 +1,5 @@ +--- +title: Add tls configuration to gitaly golang server +merge_request: 932 +author: +type: changed diff --git a/client/dial.go b/client/dial.go index 9bef70abca0..be7dd47b03e 100644 --- a/client/dial.go +++ b/client/dial.go @@ -23,7 +23,7 @@ func Dial(rawAddress string, connOpts []grpc.DialOption) (*grpc.ClientConn, erro if err != nil { return nil, err } - tls := isTls(rawAddress) + tls := isTLS(rawAddress) connOpts = append(connOpts, grpc.WithDialer(func(a string, timeout time.Duration) (net.Conn, error) { @@ -50,7 +50,7 @@ func Dial(rawAddress string, connOpts []grpc.DialOption) (*grpc.ClientConn, erro return conn, nil } -func isTls(rawAddress string) bool { +func isTLS(rawAddress string) bool { u, _ := url.Parse(rawAddress) return u.Scheme == "tls" } diff --git a/cmd/gitaly/main.go b/cmd/gitaly/main.go index f52f5066157..d9f60912206 100644 --- a/cmd/gitaly/main.go +++ b/cmd/gitaly/main.go @@ -173,7 +173,7 @@ func run(listeners, secureListeners []net.Listener) error { } defer ruby.Stop() - insecureServer := server.New(ruby, false) + insecureServer := server.New(ruby) defer insecureServer.Stop() serverErrors := make(chan error, len(listeners)+len(secureListeners)) @@ -186,7 +186,8 @@ func run(listeners, secureListeners []net.Listener) error { } if len(secureListeners) > 0 { - secureServer := server.New(ruby, true) + secureServer := server.NewSecureServer(ruby) + defer secureServer.Stop() for _, listener := range secureListeners { go func(l net.Listener) { serverErrors <- secureServer.Serve(l) diff --git a/internal/server/server.go b/internal/server/server.go index a3583c781db..fcb597ade9b 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -61,8 +61,9 @@ func init() { grpc_logrus.ReplaceGrpcLogger(log.NewEntry(gitalylog.GrpcGo)) } -// New returns a GRPC server with all Gitaly services and interceptors set up. -func New(rubyServer *rubyserver.Server, secure bool) *grpc.Server { +// createNewServer returns a GRPC server with all Gitaly services and interceptors set up. +// allows for specifying secure = true to enable tls credentials +func createNewServer(rubyServer *rubyserver.Server, secure bool) *grpc.Server { ctxTagOpts := []grpc_ctxtags.Option{ grpc_ctxtags.WithFieldExtractorForInitialReq(fieldextractors.FieldExtractor), } @@ -119,3 +120,13 @@ func New(rubyServer *rubyserver.Server, secure bool) *grpc.Server { return server } + +// New returns a GRPC server with all Gitaly services and interceptors set up. +func New(rubyServer *rubyserver.Server) *grpc.Server { + return createNewServer(rubyServer, false) +} + +// NewSecureServer returns a GRPC server enabling TLS credentials +func NewSecureServer(rubyServer *rubyserver.Server) *grpc.Server { + return createNewServer(rubyServer, true) +} -- GitLab From a24f26198b8886ef76aace8c3402b9ce9fcaf233 Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Thu, 1 Nov 2018 12:36:28 +0200 Subject: [PATCH 3/7] Followups on review --- changelogs/unreleased/gitaly-tls.yml | 2 +- client/dial.go | 3 +-- cmd/gitaly/main.go | 24 ++++++++++--------- config.toml.example | 2 +- internal/config/config.go | 6 ++--- internal/server/auth_test.go | 2 +- internal/server/server.go | 8 +++---- .../conflicts/resolve_conflicts_test.go | 2 +- .../service/operations/cherry_pick_test.go | 2 +- .../remote/fetch_internal_remote_test.go | 2 +- internal/service/repository/fetch_test.go | 2 +- 11 files changed, 28 insertions(+), 27 deletions(-) diff --git a/changelogs/unreleased/gitaly-tls.yml b/changelogs/unreleased/gitaly-tls.yml index 1ce663bc314..d54147da6c7 100644 --- a/changelogs/unreleased/gitaly-tls.yml +++ b/changelogs/unreleased/gitaly-tls.yml @@ -2,4 +2,4 @@ title: Add tls configuration to gitaly golang server merge_request: 932 author: -type: changed +type: added diff --git a/client/dial.go b/client/dial.go index be7dd47b03e..c53923958ac 100644 --- a/client/dial.go +++ b/client/dial.go @@ -23,14 +23,13 @@ func Dial(rawAddress string, connOpts []grpc.DialOption) (*grpc.ClientConn, erro if err != nil { return nil, err } - tls := isTLS(rawAddress) connOpts = append(connOpts, grpc.WithDialer(func(a string, timeout time.Duration) (net.Conn, error) { return net.DialTimeout(network, a, timeout) })) - if tls { + if isTLS(rawAddress) { certPool, err := x509.SystemCertPool() if err != nil { return nil, err diff --git a/cmd/gitaly/main.go b/cmd/gitaly/main.go index d9f60912206..37a92339735 100644 --- a/cmd/gitaly/main.go +++ b/cmd/gitaly/main.go @@ -128,7 +128,7 @@ func main() { listeners = append(listeners, connectioncounter.New("tcp", l)) } - if addr := config.Config.TLS.ListenAddr; addr != "" { + if addr := config.Config.TLSListenAddr; addr != "" { tlsListener, err := net.Listen("tcp", addr) if err != nil { log.WithError(err).Fatal("configure tls listener") @@ -173,20 +173,22 @@ func run(listeners, secureListeners []net.Listener) error { } defer ruby.Stop() - insecureServer := server.New(ruby) - defer insecureServer.Stop() + if len(insecureServer) > 0) { + insecureServer := server.NewInsecure(ruby) + defer insecureServer.Stop() - serverErrors := make(chan error, len(listeners)+len(secureListeners)) - for _, listener := range listeners { - // Must pass the listener as a function argument because there is a race - // between 'go' and 'for'. - go func(l net.Listener) { - serverErrors <- insecureServer.Serve(l) - }(listener) + serverErrors := make(chan error, len(listeners)+len(secureListeners)) + for _, listener := range listeners { + // Must pass the listener as a function argument because there is a race + // between 'go' and 'for'. + go func(l net.Listener) { + serverErrors <- insecureServer.Serve(l) + }(listener) + } } if len(secureListeners) > 0 { - secureServer := server.NewSecureServer(ruby) + secureServer := server.NewSecure(ruby) defer secureServer.Stop() for _, listener := range secureListeners { go func(l net.Listener) { diff --git a/config.toml.example b/config.toml.example index 0a354c1f1b1..4de07301afd 100644 --- a/config.toml.example +++ b/config.toml.example @@ -7,6 +7,7 @@ bin_dir = "/home/git/gitaly" # # Optional: listen on a TCP socket. This is insecure (no authentication) # listen_addr = "localhost:9999" +# tcp_listen_addr = "localhost:8888 # # Optional: export metrics via Prometheus # prometheus_listen_addr = "localhost:9236" @@ -17,7 +18,6 @@ bin_dir = "/home/git/gitaly" # transitioning = false # Set `transitioning` to true to temporarily allow unauthenticated while rolling out authentication. # [tls] -# listen_addr = 'localhost:8888' # certificate_path = '/home/git/cert.cert' # key_path = '/home/git/key.pem' diff --git a/internal/config/config.go b/internal/config/config.go index d084114350b..bac451b7493 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -21,6 +21,7 @@ var ( type config struct { SocketPath string `toml:"socket_path" split_words:"true"` ListenAddr string `toml:"listen_addr" split_words:"true"` + TLSListenAddr string `toml:"tls_listen_addr" split_words:"true"` PrometheusListenAddr string `toml:"prometheus_listen_addr" split_words:"true"` BinDir string `toml:"bin_dir"` Git Git `toml:"git" envconfig:"git"` @@ -36,9 +37,8 @@ type config struct { // TLS configuration type TLS struct { - ListenAddr string `toml:"listen_addr" split_words:"true"` - CertPath string `toml:"certificate_path"` - KeyPath string `toml:"key_path"` + CertPath string `toml:"certificate_path"` + KeyPath string `toml:"key_path"` } // GitlabShell contains the settings required for executing `gitlab-shell` diff --git a/internal/server/auth_test.go b/internal/server/auth_test.go index b50445a84fa..602db283561 100644 --- a/internal/server/auth_test.go +++ b/internal/server/auth_test.go @@ -163,7 +163,7 @@ func healthCheck(conn *grpc.ClientConn) error { } func runServer(t *testing.T) (*grpc.Server, string) { - srv := New(nil) + srv := NewInsecure(nil) serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() diff --git a/internal/server/server.go b/internal/server/server.go index fcb597ade9b..de0e1df41e7 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -121,12 +121,12 @@ func createNewServer(rubyServer *rubyserver.Server, secure bool) *grpc.Server { return server } -// New returns a GRPC server with all Gitaly services and interceptors set up. -func New(rubyServer *rubyserver.Server) *grpc.Server { +// NewInsecure returns a GRPC server with all Gitaly services and interceptors set up. +func NewInsecure(rubyServer *rubyserver.Server) *grpc.Server { return createNewServer(rubyServer, false) } -// NewSecureServer returns a GRPC server enabling TLS credentials -func NewSecureServer(rubyServer *rubyserver.Server) *grpc.Server { +// NewSecure returns a GRPC server enabling TLS credentials +func NewSecure(rubyServer *rubyserver.Server) *grpc.Server { return createNewServer(rubyServer, true) } diff --git a/internal/service/conflicts/resolve_conflicts_test.go b/internal/service/conflicts/resolve_conflicts_test.go index 02c593c6835..f53d227a26a 100644 --- a/internal/service/conflicts/resolve_conflicts_test.go +++ b/internal/service/conflicts/resolve_conflicts_test.go @@ -308,7 +308,7 @@ func TestFailedResolveConflictsRequestDueToValidation(t *testing.T) { } func runFullServer(t *testing.T) (*grpc.Server, string) { - server := serverPkg.New(conflicts.RubyServer) + server := serverPkg.NewInsecure(conflicts.RubyServer) serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() listener, err := net.Listen("unix", serverSocketPath) diff --git a/internal/service/operations/cherry_pick_test.go b/internal/service/operations/cherry_pick_test.go index 6326419ca78..3baf7e392e8 100644 --- a/internal/service/operations/cherry_pick_test.go +++ b/internal/service/operations/cherry_pick_test.go @@ -402,7 +402,7 @@ func TestFailedUserCherryPickRequestDueToCommitError(t *testing.T) { } func runFullServer(t *testing.T) (*grpc.Server, string) { - server := serverPkg.New(operations.RubyServer) + server := serverPkg.NewInsecure(operations.RubyServer) serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() listener, err := net.Listen("unix", serverSocketPath) diff --git a/internal/service/remote/fetch_internal_remote_test.go b/internal/service/remote/fetch_internal_remote_test.go index 22bfab29c76..3d2fa5688dc 100644 --- a/internal/service/remote/fetch_internal_remote_test.go +++ b/internal/service/remote/fetch_internal_remote_test.go @@ -114,7 +114,7 @@ func TestFailedFetchInternalRemoteDueToValidations(t *testing.T) { } func runFullServer(t *testing.T) (*grpc.Server, string) { - server := serverPkg.New(remote.RubyServer) + server := serverPkg.NewInsecure(remote.RubyServer) serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() listener, err := net.Listen("unix", serverSocketPath) diff --git a/internal/service/repository/fetch_test.go b/internal/service/repository/fetch_test.go index a20160ad019..c953903d4af 100644 --- a/internal/service/repository/fetch_test.go +++ b/internal/service/repository/fetch_test.go @@ -187,7 +187,7 @@ func newTestRepo(t *testing.T, relativePath string) (*gitalypb.Repository, strin } func runFullServer(t *testing.T) (*grpc.Server, string) { - server := serverPkg.New(repository.RubyServer) + server := serverPkg.NewInsecure(repository.RubyServer) serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() listener, err := net.Listen("unix", serverSocketPath) -- GitLab From da9c43edefa8c3cf82463e717159525cd9c2a4fe Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Fri, 2 Nov 2018 12:11:57 +0200 Subject: [PATCH 4/7] Add tls tests --- client/dial.go | 4 +- cmd/gitaly/main.go | 17 +++--- config.toml.example | 2 +- internal/server/auth_test.go | 44 ++++++++++++++++ internal/server/testdata/gitalycert.pem | 30 +++++++++++ internal/server/testdata/gitalykey.pem | 52 +++++++++++++++++++ .../gitlab/git/gitaly_remote_repository.rb | 9 +++- 7 files changed, 145 insertions(+), 13 deletions(-) create mode 100644 internal/server/testdata/gitalycert.pem create mode 100644 internal/server/testdata/gitalykey.pem diff --git a/client/dial.go b/client/dial.go index c53923958ac..9dc86b24f66 100644 --- a/client/dial.go +++ b/client/dial.go @@ -50,8 +50,8 @@ func Dial(rawAddress string, connOpts []grpc.DialOption) (*grpc.ClientConn, erro } func isTLS(rawAddress string) bool { - u, _ := url.Parse(rawAddress) - return u.Scheme == "tls" + u, err := url.Parse(rawAddress) + return err == nil && u.Scheme == "tls" } func parseAddress(rawAddress string) (network, addr string, err error) { diff --git a/cmd/gitaly/main.go b/cmd/gitaly/main.go index 37a92339735..c12defa28e5 100644 --- a/cmd/gitaly/main.go +++ b/cmd/gitaly/main.go @@ -106,7 +106,7 @@ func main() { tempdir.StartCleaning() - var listeners []net.Listener + var insecureListeners []net.Listener var secureListeners []net.Listener if socketPath := config.Config.SocketPath; socketPath != "" { @@ -115,7 +115,7 @@ func main() { log.WithError(err).Fatal("configure unix listener") } log.WithField("address", socketPath).Info("listening on unix socket") - listeners = append(listeners, l) + insecureListeners = append(insecureListeners, l) } if addr := config.Config.ListenAddr; addr != "" { @@ -125,7 +125,7 @@ func main() { } log.WithField("address", addr).Info("listening at tcp address") - listeners = append(listeners, connectioncounter.New("tcp", l)) + insecureListeners = append(insecureListeners, connectioncounter.New("tcp", l)) } if addr := config.Config.TLSListenAddr; addr != "" { @@ -149,7 +149,7 @@ func main() { }() } - log.WithError(run(listeners, secureListeners)).Fatal("shutting down") + log.WithError(run(insecureListeners, secureListeners)).Fatal("shutting down") } func createUnixListener(socketPath string) (net.Listener, error) { @@ -162,7 +162,7 @@ func createUnixListener(socketPath string) (net.Listener, error) { // Inside here we can use deferred functions. This is needed because // log.Fatal bypasses deferred functions. -func run(listeners, secureListeners []net.Listener) error { +func run(insecureListeners, secureListeners []net.Listener) error { signals := []os.Signal{syscall.SIGTERM, syscall.SIGINT} termCh := make(chan os.Signal, len(signals)) signal.Notify(termCh, signals...) @@ -173,12 +173,12 @@ func run(listeners, secureListeners []net.Listener) error { } defer ruby.Stop() - if len(insecureServer) > 0) { + serverErrors := make(chan error, len(insecureListeners)+len(secureListeners)) + if len(insecureListeners) > 0 { insecureServer := server.NewInsecure(ruby) defer insecureServer.Stop() - serverErrors := make(chan error, len(listeners)+len(secureListeners)) - for _, listener := range listeners { + for _, listener := range insecureListeners { // Must pass the listener as a function argument because there is a race // between 'go' and 'for'. go func(l net.Listener) { @@ -190,6 +190,7 @@ func run(listeners, secureListeners []net.Listener) error { if len(secureListeners) > 0 { secureServer := server.NewSecure(ruby) defer secureServer.Stop() + for _, listener := range secureListeners { go func(l net.Listener) { serverErrors <- secureServer.Serve(l) diff --git a/config.toml.example b/config.toml.example index 4de07301afd..1ab0fe7d6b6 100644 --- a/config.toml.example +++ b/config.toml.example @@ -7,7 +7,7 @@ bin_dir = "/home/git/gitaly" # # Optional: listen on a TCP socket. This is insecure (no authentication) # listen_addr = "localhost:9999" -# tcp_listen_addr = "localhost:8888 +# tls_listen_addr = "localhost:8888 # # Optional: export metrics via Prometheus # prometheus_listen_addr = "localhost:9236" diff --git a/internal/server/auth_test.go b/internal/server/auth_test.go index 602db283561..e0fe1e2a262 100644 --- a/internal/server/auth_test.go +++ b/internal/server/auth_test.go @@ -1,6 +1,8 @@ package server import ( + "crypto/x509" + "io/ioutil" "net" "testing" "time" @@ -13,6 +15,7 @@ import ( netctx "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" healthpb "google.golang.org/grpc/health/grpc_health_v1" ) @@ -30,6 +33,31 @@ func TestSanity(t *testing.T) { require.NoError(t, healthCheck(conn)) } +func TestTLSSanity(t *testing.T) { + srv, addr := runSecureServer(t) + defer srv.Stop() + + certPool, err := x509.SystemCertPool() + require.NoError(t, err) + + cert, err := ioutil.ReadFile("testdata/gitalycert.pem") + require.NoError(t, err) + + ok := certPool.AppendCertsFromPEM(cert) + require.True(t, ok) + + creds := credentials.NewClientTLSFromCert(certPool, "") + connOpts := []grpc.DialOption{ + grpc.WithTransportCredentials(creds), + } + + conn, err := grpc.Dial(addr, connOpts...) + require.NoError(t, err) + defer conn.Close() + + require.NoError(t, healthCheck(conn)) +} + func TestAuthFailures(t *testing.T) { defer func(oldAuth config.Auth) { config.Config.Auth = oldAuth @@ -173,3 +201,19 @@ func runServer(t *testing.T) (*grpc.Server, string) { return srv, serverSocketPath } + +func runSecureServer(t *testing.T) (*grpc.Server, string) { + config.Config.TLS = config.TLS{ + CertPath: "testdata/gitalycert.pem", + KeyPath: "testdata/gitalykey.pem", + } + + srv := NewSecure(nil) + + listener, err := net.Listen("tcp", "localhost:9999") + require.NoError(t, err) + + go srv.Serve(listener) + + return srv, "localhost:9999" +} diff --git a/internal/server/testdata/gitalycert.pem b/internal/server/testdata/gitalycert.pem new file mode 100644 index 00000000000..8b151454896 --- /dev/null +++ b/internal/server/testdata/gitalycert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFODCCAyACCQDpPfNtveVc8TANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCVVMxCzAJBgNVBAcMAlVTMQ8wDQYDVQQKDAZHaXRMYWIxDzAN +BgNVBAsMBmdpdGFseTESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTE4MTEwMjA5MDIx +MloYDzIxMTgxMDA5MDkwMjEyWjBdMQswCQYDVQQGEwJVUzELMAkGA1UECAwCVVMx +CzAJBgNVBAcMAlVTMQ8wDQYDVQQKDAZHaXRMYWIxDzANBgNVBAsMBmdpdGFseTES +MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEApJXJOWpUkV32v8gRXLWn6TEsQmy2WeilQXg96V6VOQjGZAGMEJLEjH9WHBNe +Zi4V+W+j1FB8vWTNRGTcOcpSEmDFuewoBJVA8dFtNF4jj7QQymmnKeDuOW4fWLeU +YcyGxyjlpkm2+DUg5CavT4bMZILqbsAavxJ8SKCdJpMtW3sxklnGuTHcAckHldab +9ZxH/qYqLxc5Ek2BK4OibBxA84h1RUsqe2EdzZUOoet3xpwG3Vr8bGPqR7Psghs6 +TDdWU8hYYHlReCWezgZHiYDoRqY9HCZrHSpUZ1lbRo++2j4bvdFHOAUm4BEQ6fFc +sgtW+xkNK8bxj9XTcpuDrEVscv3fyBlCMSvD+HpNbr2k1oZSOFhxISIwBLKWQBjq +5muvMRbmrG5RgWqMWjXb+g0UmlyMa2YWAWsBgSuUSjJePgbUZWHuxp/dM8CQ4lHJ +ADvfSI9ysJQM/trqjRu5BRhxiKWR72QSi1qpDPT0nKWlzQ58zs3RSuOJbWm8oOqr +XL9G/XmvgzK1qwToI/WmXBeaqmfpkagYZm+TJW0GVnDqTC+EoXdFKW7aWIjlcb4p +tYoiRA/2jjq5OqeV6iKnxz7mEJQR1xDebm6+AWgFy4zyB/QvzanaUTvNiLhyBy6Q +YwXJHkNh+KrVszBlXxkARrGesXgqOznmDeErkOKDjxzQv+cCAwEAATANBgkqhkiG +9w0BAQsFAAOCAgEAk83b9wY9iwRrx5Yep3DA3xZkVu3GJcKf0tTL8apP1MzVBSUK +5tkvW2Z4D41jpZWgJDRF8/nT2lvVwvd5xQ8/oTUerFeG/ZZ+AiBagkBKl8piPHqD +cefAO8N2SKoYHV4xBeoVU6InUuJ7xu7BLF6tY3xKvx0XsjGC7B621xmq+E56dPZg +sQwekkxODbUw4NekqYFY21BT4xiWVrTRLIGY9AfV9Ry4gqQTxda7yst4ykWh1a9e +O+426uz3jshzpQTjZwk8kCZquJKa8Qzqfdlevns0FQDP5jck4BH/YkMNsa/g9XCd +ZHSB7gqAfNoNTB1rqNKIfPUF4mTu/RWMVwxb8f6h0TfywHZ4q/4R3Zfu3jUyeVVY +ziJu2CJpcoR9SESKFbN4WFzk91nIhf2pCGo/qNO5f+n5ZPnS2jrrWL5h64e1rz2h +rVKIYLfeM2M8lVzSL1V0aJ+POcruTRsmlrFT5f7na/5YFt5N+5Z5fzixCLr1MK2w +4gFw+KhN7CAhKGzHq3NBdWpRFFMR53hyeYsb1vvwFu07JTRh+NbaePk/sk07WtCo +u2w6pD7xlayTAWcR9WRBv7c3lDejN80U8DONb8fLwtI5oIrkSuwOqvmlDOeFpKiT +MwTB6oC81Ar39P0R53247w7u9plhPUrmDn/A5KphW633UvgbkH6VmB4Isiw= +-----END CERTIFICATE----- diff --git a/internal/server/testdata/gitalykey.pem b/internal/server/testdata/gitalykey.pem new file mode 100644 index 00000000000..8df87e63353 --- /dev/null +++ b/internal/server/testdata/gitalykey.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQCklck5alSRXfa/ +yBFctafpMSxCbLZZ6KVBeD3pXpU5CMZkAYwQksSMf1YcE15mLhX5b6PUUHy9ZM1E +ZNw5ylISYMW57CgElUDx0W00XiOPtBDKaacp4O45bh9Yt5RhzIbHKOWmSbb4NSDk +Jq9PhsxkgupuwBq/EnxIoJ0mky1bezGSWca5MdwByQeV1pv1nEf+piovFzkSTYEr +g6JsHEDziHVFSyp7YR3NlQ6h63fGnAbdWvxsY+pHs+yCGzpMN1ZTyFhgeVF4JZ7O +BkeJgOhGpj0cJmsdKlRnWVtGj77aPhu90Uc4BSbgERDp8VyyC1b7GQ0rxvGP1dNy +m4OsRWxy/d/IGUIxK8P4ek1uvaTWhlI4WHEhIjAEspZAGOrma68xFuasblGBaoxa +Ndv6DRSaXIxrZhYBawGBK5RKMl4+BtRlYe7Gn90zwJDiUckAO99Ij3KwlAz+2uqN +G7kFGHGIpZHvZBKLWqkM9PScpaXNDnzOzdFK44ltabyg6qtcv0b9ea+DMrWrBOgj +9aZcF5qqZ+mRqBhmb5MlbQZWcOpML4Shd0UpbtpYiOVxvim1iiJED/aOOrk6p5Xq +IqfHPuYQlBHXEN5ubr4BaAXLjPIH9C/NqdpRO82IuHIHLpBjBckeQ2H4qtWzMGVf +GQBGsZ6xeCo7OeYN4SuQ4oOPHNC/5wIDAQABAoICAHjlPeZa4LvXFcVSJM7A8RIt ++KDiUiBA8ALjXDbsLxiyBWi4ajZSWOYLMyl0YMcV2zZadzEh3j8QqGcw30PkBd1S +EGu9uLeFGyuF9n2dGOoaDqtgaFYuz06IQaZdUzVzkx0AQZCgXTJ9dCei8uurzL+Y +GrQ3kG4CGiEPOeB4A71LBOLH511p7n2xOU0rU2xa29eGHz5wBJAZNmTMUKaxKlS5 +S8sWp6HxeH7mmtT9rgHJ4pD+oKTNz+3TkEsRzQTnMRZh9+kFtH5YxAn6OtoaQoSC +4CipX80QpuczkASI2lxdeus3quTPg/rbDl2J2dk+0ymnATHC9PX+z09ERLhqVnoD +QBY+vIw8Opj7GB/viWm/IiF0wseM+qEgr+f1qgl8pRe4N+EeD2OCnB++kslOhol+ +50KJbMJ/bfHo/3NKCqAHoKd/gk4HAiqmEKHRgSaXXjvE6bBRoFeL20zFitzCWm8z +H3CexwDRY0qJqy80Qahg+NQX58MkYskOA42fFMzuvUxfYIu/mpTTDvRK5jxsDffe +cPU2BTcbxi5hCJjo7ertid5JGp51jr6XvViuDYf77hhw8Cm5KdeavHcF1XgksRa3 +SHTtDv/Um1RvOqMzINy1Z5mFdBN8TdzEA+9gPCm+pqpD0FTDiu/IkggYeszk0syf +AIhoEJI8PkBKqQj0DxoBAoIBAQDZ96DL+fzwXN13aqhvYNTfGxZ3RCP40KCuCSrQ +gjcGcGavFOR21Y5CHaFmIFNmrtXTj3P950N+a8/KNAmm9zFx6060HEHyR6rN5b0h +BMMlp7ezyPY+VCWJWCEi3TD+4LseAynyP05rWdm2+nsGBxaXWB8yAq0CwuyYTQdZ +IZHGKeGI9irv+5mIe7bVRCVEug22u6gHmOLERXCqO70Mzi/c/UFxjGmF0LR4d7TY +VIQ5r/PPvXJzf72PVIPwJQzRsaOXnZvD1UzSahyaB3fABB6I1y6OfXyU8BukwBQ5 +J0qVRFpzMc46PFULQC5KTUIzPcnrW73UWEu5XGiAooN5SysnAoIBAQDBTaGC/5xO +755EdZggpx+05LlmoW0ijeDnuRcKPlwap4dPFUSQS2EOWxjmm4K8sNAOdiQt7Jwf +gX/S6jdY79V0rkyktWk1uCfiwu1c/rvyLdl2Jg1RW6HByL7MTBuASfuCgJsfzhev +yu+HzPJMQNFhgQTcLL9LYHp5moGKCbJzIp+FXOeFokjjlynrrmPoHV0A8To6s7q2 +yH5qu9OaOu75kWqDul5b9cXO+isxUBZbEjUy7OEa3Zezuhq8XgZVE5t2QgJOnDI/ +NNp5d16N7GcDpaEZzSNag95F0+wsFIgT29LL2zSF6h3+VjeqKTxwbRtFsMboN9TZ +QHIgBB+2ib1BAoIBAQDC31r6ovFacJxsZIZctcT8BzrJvLkwfk356yZFLvZFIn8b +r2EnQX0jbVxcczA9kLiJoirA6V91iqxHCslKZpzlTcyayNzI4Pw7g1fZSmmyo8Vg +zp4hUZgRuCJACmQArCl/BrMc6y6QWc+FgWI2HGY9P0L8slm+K0neTJfyP0oWUmFa +00PGNTqqRHlNKNTtIi6aniH3UOAFPFQjTq+R4FH4kNBO1YuOYO7I+bVM6BsjfEVO +CQFnc+ClYZloPae9XsV1Cys1JeG+CbKyn1SX7tbh3wi3ykd03UrJvBUYmCFdXLRF +Y1UOydv66BG6ymISb/60FtycGajx+0VPJHzJF8RnAoIBAQCVVyyY0HIqaeWUbmWB +lJxiXPL/32c5cvN3EwBB4bu2vAdFieDWueXZ+Xdbcnmm3dNf2NZKxKo5jQr8IAdy +ppf69U4xUhZeclAeWQqY9hSuHc4MAYn4eRqXZEhD/eihTIcLY+B0yfxyzA4SlLv9 +PXaGJe9jSw7fZUI6AKxjwOolGXK0zfnwvFgjvP2eH7T/9u+LctLR11lBLdS9ES+B +0FYgacAo1SthUJfqOEx2ZLFg2shO98NRxjEVoYpWTS4HPIa27nhp0zLesi63+QkM +DL/piWTVUi8mFwr6V6f2xkX7UbGh3VDOxPk3LdUDmaggE6smRFTnw3ql/awuIAGA +PRoBAoIBAQCwIkbP/Py8qXkrUil/ZW2+7yPXy4DeOkduLBrLBXH5fxAPUoyOywjl +CFOVcHNuioRZnN22M64VzlCgRhY4gD+ypyeFmVR4fHBWZuQObwt6jkaGXxvcGl+U +cDrMYt1xjJbjEdvX4+VjkLwJzIAzBG09agk3eLwcVAH8w5uteyKNdi0Kg9mClFJm +LRJNjI6fp5KfVitMEthx9WEe5Zu9phBLCtqYNYQoH+VY3yp8aD9NB0X5sgHYKCaK +jgQUpEnGU9zSnKeK8MglhWson3a6NEjPufsAjHgGHbTAfGEXLkiHZee3gAB2BJdk +eM9aMpgdAlLOJrfZHS3kK3968ZclB4GB +-----END PRIVATE KEY----- diff --git a/ruby/lib/gitlab/git/gitaly_remote_repository.rb b/ruby/lib/gitlab/git/gitaly_remote_repository.rb index ab0380ed741..5f2829e5e82 100644 --- a/ruby/lib/gitlab/git/gitaly_remote_repository.rb +++ b/ruby/lib/gitlab/git/gitaly_remote_repository.rb @@ -47,12 +47,17 @@ module Gitlab def address addr = gitaly_client.address(storage) - addr = addr.sub(%r{^tcp://}, '') if URI(addr).scheme == 'tcp' + addr = addr.sub(%r{^tcp://|^tls}, '') if %w[tcp tls].include? URI(addr).scheme addr end def credentials - :this_channel_is_insecure + addr = gitaly_client.address(storage) + if URI(addr).scheme == 'tls' + GRPC::Core::ChannelCredentials.new + else + :this_channel_is_insecure + end end def token -- GitLab From fbab8ed4f73b7267c1639dccc93e4f063c184197 Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Mon, 26 Nov 2018 16:20:11 +0200 Subject: [PATCH 5/7] Add tls connectivity test --- client/dial.go | 7 --- cmd/gitaly-ssh/auth_test.go | 24 +++++++-- cmd/gitaly-ssh/testdata/certs/gitalycert.pem | 30 +++++++++++ cmd/gitaly-ssh/testdata/gitalykey.pem | 52 ++++++++++++++++++++ 4 files changed, 102 insertions(+), 11 deletions(-) create mode 100755 cmd/gitaly-ssh/testdata/certs/gitalycert.pem create mode 100644 cmd/gitaly-ssh/testdata/gitalykey.pem diff --git a/client/dial.go b/client/dial.go index 152be087081..e8a3a5f4421 100644 --- a/client/dial.go +++ b/client/dial.go @@ -5,9 +5,7 @@ import ( "google.golang.org/grpc/credentials" - "net" "net/url" - "time" "google.golang.org/grpc" ) @@ -22,11 +20,6 @@ func Dial(rawAddress string, connOpts []grpc.DialOption) (*grpc.ClientConn, erro return nil, err } - connOpts = append(connOpts, - grpc.WithDialer(func(a string, timeout time.Duration) (net.Conn, error) { - return net.DialTimeout(network, a, timeout) - })) - if isTLS(rawAddress) { certPool, err := x509.SystemCertPool() if err != nil { diff --git a/cmd/gitaly-ssh/auth_test.go b/cmd/gitaly-ssh/auth_test.go index 58ff89d9a57..83a6bc9ac48 100644 --- a/cmd/gitaly-ssh/auth_test.go +++ b/cmd/gitaly-ssh/auth_test.go @@ -13,6 +13,7 @@ import ( "github.com/golang/protobuf/jsonpb" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/gitaly-proto/go/gitalypb" + "gitlab.com/gitlab-org/gitaly/internal/config" "gitlab.com/gitlab-org/gitaly/internal/rubyserver" "gitlab.com/gitlab-org/gitaly/internal/server" "gitlab.com/gitlab-org/gitaly/internal/testhelper" @@ -26,19 +27,30 @@ func buildGitalySSH(t *testing.T) { } func TestConnectivity(t *testing.T) { - buildGitalySSH(t) - testRepo := testhelper.TestRepository() + config.Config.TLS = config.TLS{ + CertPath: "testdata/certs/gitalycert.pem", + KeyPath: "testdata/gitalykey.pem", + } cwd, err := os.Getwd() require.NoError(t, err) + + certPoolPath := path.Join(cwd, "testdata/certs") + + buildGitalySSH(t) + testRepo := testhelper.TestRepository() + gitalySSHPath := path.Join(cwd, "gitaly-ssh") socketPath := testhelper.GetTemporaryGitalySocketFileName() - tcpServer, tcpPort := runServer(t, server.New, "tcp", "localhost:0") + tcpServer, tcpPort := runServer(t, server.NewInsecure, "tcp", "localhost:0") defer tcpServer.Stop() - unixServer, _ := runServer(t, server.New, "unix", socketPath) + tlsServer, tlsPort := runServer(t, server.NewSecure, "tcp", "localhost:0") + defer tlsServer.Stop() + + unixServer, _ := runServer(t, server.NewInsecure, "unix", socketPath) defer unixServer.Stop() testCases := []struct { @@ -50,6 +62,9 @@ func TestConnectivity(t *testing.T) { { addr: fmt.Sprintf("unix://%s", socketPath), }, + { + addr: fmt.Sprintf("tls://localhost:%d", tlsPort), + }, } pbMarshaler := &jsonpb.Marshaler{} @@ -66,6 +81,7 @@ func TestConnectivity(t *testing.T) { fmt.Sprintf("GITALY_ADDRESS=%s", testcase.addr), fmt.Sprintf("PATH=.:%s", os.Getenv("PATH")), fmt.Sprintf("GIT_SSH_COMMAND=%s upload-pack", gitalySSHPath), + fmt.Sprintf("SSL_CERT_DIR=%s", certPoolPath), } output, err := cmd.Output() diff --git a/cmd/gitaly-ssh/testdata/certs/gitalycert.pem b/cmd/gitaly-ssh/testdata/certs/gitalycert.pem new file mode 100755 index 00000000000..8b151454896 --- /dev/null +++ b/cmd/gitaly-ssh/testdata/certs/gitalycert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFODCCAyACCQDpPfNtveVc8TANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCVVMxCzAJBgNVBAcMAlVTMQ8wDQYDVQQKDAZHaXRMYWIxDzAN +BgNVBAsMBmdpdGFseTESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTE4MTEwMjA5MDIx +MloYDzIxMTgxMDA5MDkwMjEyWjBdMQswCQYDVQQGEwJVUzELMAkGA1UECAwCVVMx +CzAJBgNVBAcMAlVTMQ8wDQYDVQQKDAZHaXRMYWIxDzANBgNVBAsMBmdpdGFseTES +MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEApJXJOWpUkV32v8gRXLWn6TEsQmy2WeilQXg96V6VOQjGZAGMEJLEjH9WHBNe +Zi4V+W+j1FB8vWTNRGTcOcpSEmDFuewoBJVA8dFtNF4jj7QQymmnKeDuOW4fWLeU +YcyGxyjlpkm2+DUg5CavT4bMZILqbsAavxJ8SKCdJpMtW3sxklnGuTHcAckHldab +9ZxH/qYqLxc5Ek2BK4OibBxA84h1RUsqe2EdzZUOoet3xpwG3Vr8bGPqR7Psghs6 +TDdWU8hYYHlReCWezgZHiYDoRqY9HCZrHSpUZ1lbRo++2j4bvdFHOAUm4BEQ6fFc +sgtW+xkNK8bxj9XTcpuDrEVscv3fyBlCMSvD+HpNbr2k1oZSOFhxISIwBLKWQBjq +5muvMRbmrG5RgWqMWjXb+g0UmlyMa2YWAWsBgSuUSjJePgbUZWHuxp/dM8CQ4lHJ +ADvfSI9ysJQM/trqjRu5BRhxiKWR72QSi1qpDPT0nKWlzQ58zs3RSuOJbWm8oOqr +XL9G/XmvgzK1qwToI/WmXBeaqmfpkagYZm+TJW0GVnDqTC+EoXdFKW7aWIjlcb4p +tYoiRA/2jjq5OqeV6iKnxz7mEJQR1xDebm6+AWgFy4zyB/QvzanaUTvNiLhyBy6Q +YwXJHkNh+KrVszBlXxkARrGesXgqOznmDeErkOKDjxzQv+cCAwEAATANBgkqhkiG +9w0BAQsFAAOCAgEAk83b9wY9iwRrx5Yep3DA3xZkVu3GJcKf0tTL8apP1MzVBSUK +5tkvW2Z4D41jpZWgJDRF8/nT2lvVwvd5xQ8/oTUerFeG/ZZ+AiBagkBKl8piPHqD +cefAO8N2SKoYHV4xBeoVU6InUuJ7xu7BLF6tY3xKvx0XsjGC7B621xmq+E56dPZg +sQwekkxODbUw4NekqYFY21BT4xiWVrTRLIGY9AfV9Ry4gqQTxda7yst4ykWh1a9e +O+426uz3jshzpQTjZwk8kCZquJKa8Qzqfdlevns0FQDP5jck4BH/YkMNsa/g9XCd +ZHSB7gqAfNoNTB1rqNKIfPUF4mTu/RWMVwxb8f6h0TfywHZ4q/4R3Zfu3jUyeVVY +ziJu2CJpcoR9SESKFbN4WFzk91nIhf2pCGo/qNO5f+n5ZPnS2jrrWL5h64e1rz2h +rVKIYLfeM2M8lVzSL1V0aJ+POcruTRsmlrFT5f7na/5YFt5N+5Z5fzixCLr1MK2w +4gFw+KhN7CAhKGzHq3NBdWpRFFMR53hyeYsb1vvwFu07JTRh+NbaePk/sk07WtCo +u2w6pD7xlayTAWcR9WRBv7c3lDejN80U8DONb8fLwtI5oIrkSuwOqvmlDOeFpKiT +MwTB6oC81Ar39P0R53247w7u9plhPUrmDn/A5KphW633UvgbkH6VmB4Isiw= +-----END CERTIFICATE----- diff --git a/cmd/gitaly-ssh/testdata/gitalykey.pem b/cmd/gitaly-ssh/testdata/gitalykey.pem new file mode 100644 index 00000000000..8df87e63353 --- /dev/null +++ b/cmd/gitaly-ssh/testdata/gitalykey.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +eM9aMpgdAlLOJrfZHS3kK3968ZclB4GB +-----END PRIVATE KEY----- -- GitLab From a1bc63a7f429c7e031ef96d80d5dead289fafd4e Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Mon, 26 Nov 2018 20:36:34 +0200 Subject: [PATCH 6/7] TLS tests for remote repository client --- client/address_parser_test.go | 1 + internal/server/server.go | 4 +- .../gitlab/git/gitaly_remote_repository.rb | 21 ++++++-- .../git/remote_repository_client_spec.rb | 22 +++++++- .../gitlab/git/testdata/certs/gitalycert.pem | 30 +++++++++++ .../spec/support/helpers/certs/gitalycert.pem | 30 +++++++++++ ruby/spec/support/helpers/certs/gitalykey.pem | 52 +++++++++++++++++++ .../support/helpers/integration_helper.rb | 43 +++++++++++---- 8 files changed, 184 insertions(+), 19 deletions(-) create mode 100755 ruby/spec/lib/gitlab/git/testdata/certs/gitalycert.pem create mode 100755 ruby/spec/support/helpers/certs/gitalycert.pem create mode 100644 ruby/spec/support/helpers/certs/gitalykey.pem diff --git a/client/address_parser_test.go b/client/address_parser_test.go index d840d1d05ac..820a902b3c4 100644 --- a/client/address_parser_test.go +++ b/client/address_parser_test.go @@ -20,6 +20,7 @@ func TestParseAddress(t *testing.T) { {raw: "tcp://foobar", canonical: "foobar"}, {raw: "tcp://foobar:567", canonical: "foobar:567"}, {raw: "tcp://1.2.3.4/foo/bar.socket", invalid: true}, + {raw: "tls://1.2.3.4/foo/bar.socket", invalid: true}, {raw: "tcp:///foo/bar.socket", invalid: true}, {raw: "tcp:/foo/bar.socket", invalid: true}, {raw: "tcp://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:9999", canonical: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:9999"}, diff --git a/internal/server/server.go b/internal/server/server.go index fbb9c6ab7db..6fb8be2df0e 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -112,9 +112,7 @@ func createNewServer(rubyServer *rubyserver.Server, secure bool) *grpc.Server { opts = append(opts, grpc.Creds(credentials.NewServerTLSFromCert(&cert))) } - server := grpc.NewServer( - opts..., - ) + server := grpc.NewServer(opts...) service.RegisterAll(server, rubyServer) reflection.Register(server) diff --git a/ruby/lib/gitlab/git/gitaly_remote_repository.rb b/ruby/lib/gitlab/git/gitaly_remote_repository.rb index 5f2829e5e82..dbb93ecb2a0 100644 --- a/ruby/lib/gitlab/git/gitaly_remote_repository.rb +++ b/ruby/lib/gitlab/git/gitaly_remote_repository.rb @@ -47,14 +47,27 @@ module Gitlab def address addr = gitaly_client.address(storage) - addr = addr.sub(%r{^tcp://|^tls}, '') if %w[tcp tls].include? URI(addr).scheme + addr = addr.sub(%r{^tcp://|^tls://}, '') if %w[tcp tls].include? URI(addr).scheme addr end + def load_certs + raise 'SSL_CERT_DIR and/or SSL_CERT_FILE environment variable must be set' unless ENV['SSL_CERT_DIR'] || ENV['SSL_CERT_FILE'] + + return @certs if @certs + + files = [] + files += Dir["#{ENV['SSL_CERT_DIR']}/*"] if ENV['SSL_CERT_DIR'] + files += [ENV['SSL_CERT_FILE']] if ENV['SSL_CERT_FILE'] + + @certs = files.map do |cert| + File.read(cert) + end.join("\n") + end + def credentials - addr = gitaly_client.address(storage) - if URI(addr).scheme == 'tls' - GRPC::Core::ChannelCredentials.new + if URI(gitaly_client.address(storage)).scheme == 'tls' + GRPC::Core::ChannelCredentials.new load_certs else :this_channel_is_insecure end diff --git a/ruby/spec/lib/gitlab/git/remote_repository_client_spec.rb b/ruby/spec/lib/gitlab/git/remote_repository_client_spec.rb index dddb21c00f9..4d6851186cf 100644 --- a/ruby/spec/lib/gitlab/git/remote_repository_client_spec.rb +++ b/ruby/spec/lib/gitlab/git/remote_repository_client_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::Git::GitalyRemoteRepository do describe 'Connectivity' do context 'tcp' do let(:client) do - get_client("tcp://localhost:#{GitalyConfig.dynamic_port}") + get_client("tcp://localhost:#{GitalyConfig.dynamic_port('tcp')}") end it 'Should connect over tcp' do @@ -23,5 +23,25 @@ describe Gitlab::Git::GitalyRemoteRepository do expect(client).not_to be_empty end end + + context 'tls' do + let(:client) { get_client("tls://localhost:#{GitalyConfig.dynamic_port('tls')}") } + + it 'Should connect over tls using SSL_CERT_DIR' do + cert_pool_dir = File.join(File.dirname(__FILE__), "testdata/certs") + allow(ENV).to receive(:[]).with('SSL_CERT_DIR').and_return(cert_pool_dir) + allow(ENV).to receive(:[]).with('SSL_CERT_FILE').and_return(nil) + + expect(client).not_to be_empty + end + + it 'Should connect over tls using SSL_CERT_FILE' do + cert = File.join(File.dirname(__FILE__), "testdata/certs/gitalycert.pem") + allow(ENV).to receive(:[]).with('SSL_CERT_DIR').and_return(nil) + allow(ENV).to receive(:[]).with('SSL_CERT_FILE').and_return(cert) + + expect(client).not_to be_empty + end + end end end diff --git a/ruby/spec/lib/gitlab/git/testdata/certs/gitalycert.pem b/ruby/spec/lib/gitlab/git/testdata/certs/gitalycert.pem new file mode 100755 index 00000000000..8b151454896 --- /dev/null +++ b/ruby/spec/lib/gitlab/git/testdata/certs/gitalycert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFODCCAyACCQDpPfNtveVc8TANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCVVMxCzAJBgNVBAcMAlVTMQ8wDQYDVQQKDAZHaXRMYWIxDzAN +BgNVBAsMBmdpdGFseTESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTE4MTEwMjA5MDIx +MloYDzIxMTgxMDA5MDkwMjEyWjBdMQswCQYDVQQGEwJVUzELMAkGA1UECAwCVVMx +CzAJBgNVBAcMAlVTMQ8wDQYDVQQKDAZHaXRMYWIxDzANBgNVBAsMBmdpdGFseTES +MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEApJXJOWpUkV32v8gRXLWn6TEsQmy2WeilQXg96V6VOQjGZAGMEJLEjH9WHBNe +Zi4V+W+j1FB8vWTNRGTcOcpSEmDFuewoBJVA8dFtNF4jj7QQymmnKeDuOW4fWLeU +YcyGxyjlpkm2+DUg5CavT4bMZILqbsAavxJ8SKCdJpMtW3sxklnGuTHcAckHldab +9ZxH/qYqLxc5Ek2BK4OibBxA84h1RUsqe2EdzZUOoet3xpwG3Vr8bGPqR7Psghs6 +TDdWU8hYYHlReCWezgZHiYDoRqY9HCZrHSpUZ1lbRo++2j4bvdFHOAUm4BEQ6fFc +sgtW+xkNK8bxj9XTcpuDrEVscv3fyBlCMSvD+HpNbr2k1oZSOFhxISIwBLKWQBjq +5muvMRbmrG5RgWqMWjXb+g0UmlyMa2YWAWsBgSuUSjJePgbUZWHuxp/dM8CQ4lHJ +ADvfSI9ysJQM/trqjRu5BRhxiKWR72QSi1qpDPT0nKWlzQ58zs3RSuOJbWm8oOqr +XL9G/XmvgzK1qwToI/WmXBeaqmfpkagYZm+TJW0GVnDqTC+EoXdFKW7aWIjlcb4p +tYoiRA/2jjq5OqeV6iKnxz7mEJQR1xDebm6+AWgFy4zyB/QvzanaUTvNiLhyBy6Q +YwXJHkNh+KrVszBlXxkARrGesXgqOznmDeErkOKDjxzQv+cCAwEAATANBgkqhkiG +9w0BAQsFAAOCAgEAk83b9wY9iwRrx5Yep3DA3xZkVu3GJcKf0tTL8apP1MzVBSUK +5tkvW2Z4D41jpZWgJDRF8/nT2lvVwvd5xQ8/oTUerFeG/ZZ+AiBagkBKl8piPHqD +cefAO8N2SKoYHV4xBeoVU6InUuJ7xu7BLF6tY3xKvx0XsjGC7B621xmq+E56dPZg +sQwekkxODbUw4NekqYFY21BT4xiWVrTRLIGY9AfV9Ry4gqQTxda7yst4ykWh1a9e +O+426uz3jshzpQTjZwk8kCZquJKa8Qzqfdlevns0FQDP5jck4BH/YkMNsa/g9XCd +ZHSB7gqAfNoNTB1rqNKIfPUF4mTu/RWMVwxb8f6h0TfywHZ4q/4R3Zfu3jUyeVVY +ziJu2CJpcoR9SESKFbN4WFzk91nIhf2pCGo/qNO5f+n5ZPnS2jrrWL5h64e1rz2h +rVKIYLfeM2M8lVzSL1V0aJ+POcruTRsmlrFT5f7na/5YFt5N+5Z5fzixCLr1MK2w +4gFw+KhN7CAhKGzHq3NBdWpRFFMR53hyeYsb1vvwFu07JTRh+NbaePk/sk07WtCo +u2w6pD7xlayTAWcR9WRBv7c3lDejN80U8DONb8fLwtI5oIrkSuwOqvmlDOeFpKiT +MwTB6oC81Ar39P0R53247w7u9plhPUrmDn/A5KphW633UvgbkH6VmB4Isiw= +-----END CERTIFICATE----- diff --git a/ruby/spec/support/helpers/certs/gitalycert.pem b/ruby/spec/support/helpers/certs/gitalycert.pem new file mode 100755 index 00000000000..8b151454896 --- /dev/null +++ b/ruby/spec/support/helpers/certs/gitalycert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFODCCAyACCQDpPfNtveVc8TANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCVVMxCzAJBgNVBAcMAlVTMQ8wDQYDVQQKDAZHaXRMYWIxDzAN +BgNVBAsMBmdpdGFseTESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTE4MTEwMjA5MDIx +MloYDzIxMTgxMDA5MDkwMjEyWjBdMQswCQYDVQQGEwJVUzELMAkGA1UECAwCVVMx +CzAJBgNVBAcMAlVTMQ8wDQYDVQQKDAZHaXRMYWIxDzANBgNVBAsMBmdpdGFseTES +MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEApJXJOWpUkV32v8gRXLWn6TEsQmy2WeilQXg96V6VOQjGZAGMEJLEjH9WHBNe +Zi4V+W+j1FB8vWTNRGTcOcpSEmDFuewoBJVA8dFtNF4jj7QQymmnKeDuOW4fWLeU +YcyGxyjlpkm2+DUg5CavT4bMZILqbsAavxJ8SKCdJpMtW3sxklnGuTHcAckHldab +9ZxH/qYqLxc5Ek2BK4OibBxA84h1RUsqe2EdzZUOoet3xpwG3Vr8bGPqR7Psghs6 +TDdWU8hYYHlReCWezgZHiYDoRqY9HCZrHSpUZ1lbRo++2j4bvdFHOAUm4BEQ6fFc +sgtW+xkNK8bxj9XTcpuDrEVscv3fyBlCMSvD+HpNbr2k1oZSOFhxISIwBLKWQBjq +5muvMRbmrG5RgWqMWjXb+g0UmlyMa2YWAWsBgSuUSjJePgbUZWHuxp/dM8CQ4lHJ +ADvfSI9ysJQM/trqjRu5BRhxiKWR72QSi1qpDPT0nKWlzQ58zs3RSuOJbWm8oOqr +XL9G/XmvgzK1qwToI/WmXBeaqmfpkagYZm+TJW0GVnDqTC+EoXdFKW7aWIjlcb4p +tYoiRA/2jjq5OqeV6iKnxz7mEJQR1xDebm6+AWgFy4zyB/QvzanaUTvNiLhyBy6Q +YwXJHkNh+KrVszBlXxkARrGesXgqOznmDeErkOKDjxzQv+cCAwEAATANBgkqhkiG +9w0BAQsFAAOCAgEAk83b9wY9iwRrx5Yep3DA3xZkVu3GJcKf0tTL8apP1MzVBSUK +5tkvW2Z4D41jpZWgJDRF8/nT2lvVwvd5xQ8/oTUerFeG/ZZ+AiBagkBKl8piPHqD +cefAO8N2SKoYHV4xBeoVU6InUuJ7xu7BLF6tY3xKvx0XsjGC7B621xmq+E56dPZg +sQwekkxODbUw4NekqYFY21BT4xiWVrTRLIGY9AfV9Ry4gqQTxda7yst4ykWh1a9e +O+426uz3jshzpQTjZwk8kCZquJKa8Qzqfdlevns0FQDP5jck4BH/YkMNsa/g9XCd +ZHSB7gqAfNoNTB1rqNKIfPUF4mTu/RWMVwxb8f6h0TfywHZ4q/4R3Zfu3jUyeVVY +ziJu2CJpcoR9SESKFbN4WFzk91nIhf2pCGo/qNO5f+n5ZPnS2jrrWL5h64e1rz2h +rVKIYLfeM2M8lVzSL1V0aJ+POcruTRsmlrFT5f7na/5YFt5N+5Z5fzixCLr1MK2w +4gFw+KhN7CAhKGzHq3NBdWpRFFMR53hyeYsb1vvwFu07JTRh+NbaePk/sk07WtCo +u2w6pD7xlayTAWcR9WRBv7c3lDejN80U8DONb8fLwtI5oIrkSuwOqvmlDOeFpKiT +MwTB6oC81Ar39P0R53247w7u9plhPUrmDn/A5KphW633UvgbkH6VmB4Isiw= +-----END CERTIFICATE----- diff --git a/ruby/spec/support/helpers/certs/gitalykey.pem b/ruby/spec/support/helpers/certs/gitalykey.pem new file mode 100644 index 00000000000..8df87e63353 --- /dev/null +++ b/ruby/spec/support/helpers/certs/gitalykey.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +eM9aMpgdAlLOJrfZHS3kK3968ZclB4GB +-----END PRIVATE KEY----- diff --git a/ruby/spec/support/helpers/integration_helper.rb b/ruby/spec/support/helpers/integration_helper.rb index 9c717261f94..6e6631ba9a6 100644 --- a/ruby/spec/support/helpers/integration_helper.rb +++ b/ruby/spec/support/helpers/integration_helper.rb @@ -6,14 +6,28 @@ require 'spec_helper' SOCKET_PATH = 'gitaly.socket'.freeze module GitalyConfig - def self.dynamic_port - @dynamic_port ||= begin - sock = Socket.new(:INET, :STREAM) - sock.bind(Addrinfo.tcp('127.0.0.1', 0)) - sock.local_address.ip_port - ensure - sock.close - end + def self.set_dynamic_ports + tcp_sock = Socket.new(:INET, :STREAM) + tls_sock = Socket.new(:INET, :STREAM) + tcp_sock.bind(Addrinfo.tcp('127.0.0.1', 0)) + tls_sock.bind(Addrinfo.tcp('127.0.0.1', 0)) + + @dynamic_tcp_port = tcp_sock.local_address.ip_port + @dynamic_tls_port = tls_sock.local_address.ip_port + ensure + tcp_sock.close + tls_sock.close + end + + def self.dynamic_port(type) + set_dynamic_ports unless @dynamic_tcp_port && @dynamic_tls_port + + case type + when 'tcp' + @dynamic_tcp_port + when 'tls' + @dynamic_tls_port + end end end @@ -23,8 +37,8 @@ module IntegrationClient addr = case type when 'unix' "unix:#{File.join(TMP_DIR_NAME, SOCKET_PATH)}" - when 'tcp' - "tcp://localhost:#{GitalyConfig.dynamic_port}" + when 'tcp', 'tls' + "#{type}://localhost:#{GitalyConfig.dynamic_port(type)}" end klass.new(addr, creds) end @@ -54,11 +68,18 @@ def start_gitaly build_dir = File.expand_path(File.join(GITALY_RUBY_DIR, '../_build')) GitlabShellHelper.setup_gitlab_shell + cert_path = File.join(File.dirname(__FILE__), "/certs") + config_toml = <<~CONFIG socket_path = "#{SOCKET_PATH}" - listen_addr = "localhost:#{GitalyConfig.dynamic_port}" + listen_addr = "localhost:#{GitalyConfig.dynamic_port('tcp')}" + tls_listen_addr = "localhost:#{GitalyConfig.dynamic_port('tls')}" bin_dir = "#{build_dir}/bin" + [tls] + certificate_path = "#{cert_path}/gitalycert.pem" + key_path = "#{cert_path}/gitalykey.pem" + [gitlab-shell] dir = "#{GITLAB_SHELL_DIR}" -- GitLab From 8b71b2797779b56d3ad7cba8a67d1a7665041b7d Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Tue, 4 Dec 2018 13:32:20 +0200 Subject: [PATCH 7/7] Add unit tests for certs method --- .../gitlab/git/gitaly_remote_repository.rb | 43 ++++++------- .../git/remote_repository_client_spec.rb | 61 ++++++++++++++++--- .../gitlab/git/testdata/certs/gitalycert2.pem | 30 +++++++++ .../lib/gitlab/git/testdata/gitalycert.pem | 30 +++++++++ 4 files changed, 134 insertions(+), 30 deletions(-) create mode 100755 ruby/spec/lib/gitlab/git/testdata/certs/gitalycert2.pem create mode 100755 ruby/spec/lib/gitlab/git/testdata/gitalycert.pem diff --git a/ruby/lib/gitlab/git/gitaly_remote_repository.rb b/ruby/lib/gitlab/git/gitaly_remote_repository.rb index dbb93ecb2a0..694bb74c68e 100644 --- a/ruby/lib/gitlab/git/gitaly_remote_repository.rb +++ b/ruby/lib/gitlab/git/gitaly_remote_repository.rb @@ -31,27 +31,8 @@ module Gitlab stub.find_commit(request, request_kwargs)&.commit&.id.presence end - private - - def exists? - request = Gitaly::RepositoryExistsRequest.new(repository: @gitaly_repository) - stub = Gitaly::RepositoryService::Stub.new(address, credentials) - stub.repository_exists(request, request_kwargs).exists - end - - def has_visible_content? - request = Gitaly::HasLocalBranchesRequest.new(repository: @gitaly_repository) - stub = Gitaly::RepositoryService::Stub.new(address, credentials) - stub.has_local_branches(request, request_kwargs).value - end - - def address - addr = gitaly_client.address(storage) - addr = addr.sub(%r{^tcp://|^tls://}, '') if %w[tcp tls].include? URI(addr).scheme - addr - end - def load_certs + def certs raise 'SSL_CERT_DIR and/or SSL_CERT_FILE environment variable must be set' unless ENV['SSL_CERT_DIR'] || ENV['SSL_CERT_FILE'] return @certs if @certs @@ -67,12 +48,32 @@ module Gitlab def credentials if URI(gitaly_client.address(storage)).scheme == 'tls' - GRPC::Core::ChannelCredentials.new load_certs + GRPC::Core::ChannelCredentials.new certs else :this_channel_is_insecure end end + private + + def exists? + request = Gitaly::RepositoryExistsRequest.new(repository: @gitaly_repository) + stub = Gitaly::RepositoryService::Stub.new(address, credentials) + stub.repository_exists(request, request_kwargs).exists + end + + def has_visible_content? + request = Gitaly::HasLocalBranchesRequest.new(repository: @gitaly_repository) + stub = Gitaly::RepositoryService::Stub.new(address, credentials) + stub.has_local_branches(request, request_kwargs).value + end + + def address + addr = gitaly_client.address(storage) + addr = addr.sub(%r{^tcp://|^tls://}, '') if %w[tcp tls].include? URI(addr).scheme + addr + end + def token gitaly_client.token(storage).to_s end diff --git a/ruby/spec/lib/gitlab/git/remote_repository_client_spec.rb b/ruby/spec/lib/gitlab/git/remote_repository_client_spec.rb index 4d6851186cf..a4bd23e22ed 100644 --- a/ruby/spec/lib/gitlab/git/remote_repository_client_spec.rb +++ b/ruby/spec/lib/gitlab/git/remote_repository_client_spec.rb @@ -5,6 +5,57 @@ describe Gitlab::Git::GitalyRemoteRepository do include IntegrationClient let(:repository) { gitlab_git_from_gitaly_with_gitlab_projects(new_mutable_test_repo) } + describe 'certs' do + let(:client) { get_client("tls://localhost:#{GitalyConfig.dynamic_port('tls')}") } + + context 'when neither SSL_CERT_FILE and SSL_CERT_DIR is set' do + it 'Raises an error' do + expect { client.certs }.to raise_error 'SSL_CERT_DIR and/or SSL_CERT_FILE environment variable must be set' + end + end + + context 'when SSL_CERT_FILE is set' do + it 'Should return the correct certificate' do + cert = File.join(File.dirname(__FILE__), "testdata/certs/gitalycert.pem") + allow(ENV).to receive(:[]).with('SSL_CERT_DIR').and_return(nil) + allow(ENV).to receive(:[]).with('SSL_CERT_FILE').and_return(cert) + certs = client.certs + expect(certs).to eq File.read(cert) + end + end + + context 'when SSL_CERT_DIR is set' do + it 'Should return concatination of gitalycert and gitalycert2' do + cert_pool_dir = File.join(File.dirname(__FILE__), "testdata/certs") + allow(ENV).to receive(:[]).with('SSL_CERT_DIR').and_return(cert_pool_dir) + allow(ENV).to receive(:[]).with('SSL_CERT_FILE').and_return(nil) + certs = client.certs + expected_certs = Dir["#{cert_pool_dir}/*"].map do |cert| + File.read cert + end.join("\n") + + expect(certs).to eq expected_certs + end + end + + context 'when both SSL_CERT_DIR and SSL_CERT_FILE are set' do + it 'Should return all certs in SSL_CERT_DIR + SSL_CERT_FILE' do + cert_pool_dir = File.join(File.dirname(__FILE__), "testdata/certs") + cert1_file = File.join(File.dirname(__FILE__), "testdata/gitalycert.pem") + allow(ENV).to receive(:[]).with('SSL_CERT_DIR').and_return(cert_pool_dir) + allow(ENV).to receive(:[]).with('SSL_CERT_FILE').and_return(cert1_file) + expected_certs_paths = Dir["#{cert_pool_dir}/*"] + expected_certs_paths += [cert1_file] + + expected_certs = expected_certs_paths.map do |cert| + File.read cert + end.join("\n") + certs = client.certs + expect(certs).to eq expected_certs + end + end + end + describe 'Connectivity' do context 'tcp' do let(:client) do @@ -27,15 +78,7 @@ describe Gitlab::Git::GitalyRemoteRepository do context 'tls' do let(:client) { get_client("tls://localhost:#{GitalyConfig.dynamic_port('tls')}") } - it 'Should connect over tls using SSL_CERT_DIR' do - cert_pool_dir = File.join(File.dirname(__FILE__), "testdata/certs") - allow(ENV).to receive(:[]).with('SSL_CERT_DIR').and_return(cert_pool_dir) - allow(ENV).to receive(:[]).with('SSL_CERT_FILE').and_return(nil) - - expect(client).not_to be_empty - end - - it 'Should connect over tls using SSL_CERT_FILE' do + it 'Should connect over tls' do cert = File.join(File.dirname(__FILE__), "testdata/certs/gitalycert.pem") allow(ENV).to receive(:[]).with('SSL_CERT_DIR').and_return(nil) allow(ENV).to receive(:[]).with('SSL_CERT_FILE').and_return(cert) diff --git a/ruby/spec/lib/gitlab/git/testdata/certs/gitalycert2.pem b/ruby/spec/lib/gitlab/git/testdata/certs/gitalycert2.pem new file mode 100755 index 00000000000..4708f8ec36d --- /dev/null +++ b/ruby/spec/lib/gitlab/git/testdata/certs/gitalycert2.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFODCCAyACCQDpPfNtveVc8TANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCVVMxCzAJBgNVBAcMAlVTMQ8wDQYDVQQKDAZHaXRMYWIxDzAN +BgNVBAsMBmdpdGFseTESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTE4MTEwMjA5MDIx +MloYDzIxMTgxMDA5MDkwMjEyWjBdMQswCQYDVQQGEwJVUzELMAkGA1UECAwCVVMx +CzAJBgNVBAcMAlVTMQ8wDQYDVQQKDAZHaXRMYWIxDzANBgNVBAsMBmdpdGFseTES +MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEApJXJOWpUkV32v8gRXLWn6TEsQmy2WeilQXg96V6VOQjGZAGMEJLEjH9WHBNe +Zi4V+W+j1FB8vWTNRGTcOcpSEmDFuewoBJVA8dFtNF4jj7QQymmnKeDuOW4fWLeU +Ykkjkxyjlpkm2+DUg5CavT4bMZILqbsAavxJ8SKCdJpMtW3sxklnGuTHcAckHldab +9ZxH/qYqLxc5Ek2BK4OibBxA84h1RUsqe2EdzZUOoet3xpwG3Vr8bGPqR7Psghs6 +TDdWU8hYYHlReCWezgZHiYDoRqY9HCZrHSpUZ1lbRo++2j4bvdFHOAUm4BEQ6fFc +sgtW+xkNK8bxj9XTcpuDrEVscv3fyBlCMSvD+HpNbr2k1oZSOFhxISIwBLKWQBjq +5muvMRbmrG5RgWqMWjXb+g0UmlyMa2YWAWsBgSuUSjJePgbUZWHuxp/dM8CQ4lHJ +ADvfSI9ysJQM/trqjRu5BRhxiKWR72QSi1qpDPT0nKWlzQ58zs3RSuOJbWm8oOqr +XL9G/XmvgzK1qwToI/WmXBeaqmfpkagYZm+TJW0GVnDqTC+EoXdFKW7aWIjlcb4p +tYoiRA/2jjq5OqeV6iKnxz7mEJQR1xDebm6+AWgFy4zyB/QvzanaUTvNiLhyBy6Q +YwXJHkNh+KrVszBlXxkARrGesXgqOznmDeErkOKDjxzQv+cCAwEAATANBgkqhkiG +9w0BAQsFAAOCAgEAk83b9wY9iwRrx5Yep3DA3xZkVu3GJcKf0tTL8apP1MzVBSUK +5tkvW2Z4D41jpZWgJDRF8/nT2lvVwvd5xQ8/oTUerFeG/ZZ+AiBagkBKl8piPHqD +cefAO8N2SKoYHV4xBeoVU6InUuJ7xu7BLF6tY3xKvx0XsjGC7B621xmq+E56dPZg +sQwekkxODbUw4NekqYFY21BT4xiWVrTRLIGY9AfV9Ry4gqQTxda7yst4ykWh1a9e +O+426uz3jshzpQTjZwk8kCZquJKa8Qzqfdlevns0FQDP5jck4BH/YkMNsa/g9XCd +ZHSB7gqAfNoNTB1rqNKIfPUF4mTu/RWMVwxb8f6h0TfywHZ4q/4R3Zfu3jUyeVVY +ziJu2CJpcoR9SESKFbN4WFzk91nIhf2pCGo/qNO5f+n5ZPnS2jrrWL5h64e1rz2h +rVKIYLfeM2M8lVzSL1V0aJ+POcruTRsmlrFT5f7na/5YFt5N+5Z5fzixCLr1MK2w +4gFw+KhN7CAhKGzHq3NBdWpRFFMR53hyeYsb1vvwFu07JTRh+NbaePk/sk07WtCo +u2w6pD7xlayTAWcR9WRBv7c3lDejN80U8DONb8fLwtI5oIrkSuwOqvmlDOeFpKiT +MwTB6oC81Ar39P0R53247w7u9plhPUrmDn/A5KphW633UvgbkH6VmB4Isiw= +-----END CERTIFICATE----- diff --git a/ruby/spec/lib/gitlab/git/testdata/gitalycert.pem b/ruby/spec/lib/gitlab/git/testdata/gitalycert.pem new file mode 100755 index 00000000000..8b151454896 --- /dev/null +++ b/ruby/spec/lib/gitlab/git/testdata/gitalycert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFODCCAyACCQDpPfNtveVc8TANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCVVMxCzAJBgNVBAcMAlVTMQ8wDQYDVQQKDAZHaXRMYWIxDzAN +BgNVBAsMBmdpdGFseTESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTE4MTEwMjA5MDIx +MloYDzIxMTgxMDA5MDkwMjEyWjBdMQswCQYDVQQGEwJVUzELMAkGA1UECAwCVVMx +CzAJBgNVBAcMAlVTMQ8wDQYDVQQKDAZHaXRMYWIxDzANBgNVBAsMBmdpdGFseTES +MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEApJXJOWpUkV32v8gRXLWn6TEsQmy2WeilQXg96V6VOQjGZAGMEJLEjH9WHBNe +Zi4V+W+j1FB8vWTNRGTcOcpSEmDFuewoBJVA8dFtNF4jj7QQymmnKeDuOW4fWLeU +YcyGxyjlpkm2+DUg5CavT4bMZILqbsAavxJ8SKCdJpMtW3sxklnGuTHcAckHldab +9ZxH/qYqLxc5Ek2BK4OibBxA84h1RUsqe2EdzZUOoet3xpwG3Vr8bGPqR7Psghs6 +TDdWU8hYYHlReCWezgZHiYDoRqY9HCZrHSpUZ1lbRo++2j4bvdFHOAUm4BEQ6fFc +sgtW+xkNK8bxj9XTcpuDrEVscv3fyBlCMSvD+HpNbr2k1oZSOFhxISIwBLKWQBjq +5muvMRbmrG5RgWqMWjXb+g0UmlyMa2YWAWsBgSuUSjJePgbUZWHuxp/dM8CQ4lHJ +ADvfSI9ysJQM/trqjRu5BRhxiKWR72QSi1qpDPT0nKWlzQ58zs3RSuOJbWm8oOqr +XL9G/XmvgzK1qwToI/WmXBeaqmfpkagYZm+TJW0GVnDqTC+EoXdFKW7aWIjlcb4p +tYoiRA/2jjq5OqeV6iKnxz7mEJQR1xDebm6+AWgFy4zyB/QvzanaUTvNiLhyBy6Q +YwXJHkNh+KrVszBlXxkARrGesXgqOznmDeErkOKDjxzQv+cCAwEAATANBgkqhkiG +9w0BAQsFAAOCAgEAk83b9wY9iwRrx5Yep3DA3xZkVu3GJcKf0tTL8apP1MzVBSUK +5tkvW2Z4D41jpZWgJDRF8/nT2lvVwvd5xQ8/oTUerFeG/ZZ+AiBagkBKl8piPHqD +cefAO8N2SKoYHV4xBeoVU6InUuJ7xu7BLF6tY3xKvx0XsjGC7B621xmq+E56dPZg +sQwekkxODbUw4NekqYFY21BT4xiWVrTRLIGY9AfV9Ry4gqQTxda7yst4ykWh1a9e +O+426uz3jshzpQTjZwk8kCZquJKa8Qzqfdlevns0FQDP5jck4BH/YkMNsa/g9XCd +ZHSB7gqAfNoNTB1rqNKIfPUF4mTu/RWMVwxb8f6h0TfywHZ4q/4R3Zfu3jUyeVVY +ziJu2CJpcoR9SESKFbN4WFzk91nIhf2pCGo/qNO5f+n5ZPnS2jrrWL5h64e1rz2h +rVKIYLfeM2M8lVzSL1V0aJ+POcruTRsmlrFT5f7na/5YFt5N+5Z5fzixCLr1MK2w +4gFw+KhN7CAhKGzHq3NBdWpRFFMR53hyeYsb1vvwFu07JTRh+NbaePk/sk07WtCo +u2w6pD7xlayTAWcR9WRBv7c3lDejN80U8DONb8fLwtI5oIrkSuwOqvmlDOeFpKiT +MwTB6oC81Ar39P0R53247w7u9plhPUrmDn/A5KphW633UvgbkH6VmB4Isiw= +-----END CERTIFICATE----- -- GitLab