diff options
author | John Cai <jcai@gitlab.com> | 2019-10-08 20:23:05 +0300 |
---|---|---|
committer | John Cai <jcai@gitlab.com> | 2019-10-08 20:23:05 +0300 |
commit | d1bca34e27326dfd58738f8bab7a8e27a40f2990 (patch) | |
tree | f7619243567a2096c38c36bc42129ec50e1873e3 | |
parent | 58ccfe7fdbde5a3f1d492d7a196ea24f8fab4e34 (diff) | |
parent | ad371133b96a7c9262269a1ffca5612c7eaeb9bc (diff) |
Merge branch 'jc-praefect-auth' into 'master'
Adding auth to praefect
Closes #1897
See merge request gitlab-org/gitaly!1517
-rw-r--r-- | changelogs/unreleased/jc-praefect-auth.yml | 5 | ||||
-rw-r--r-- | cmd/praefect/main.go | 2 | ||||
-rw-r--r-- | config.praefect.toml.example | 11 | ||||
-rw-r--r-- | internal/auth/auth.go | 7 | ||||
-rw-r--r-- | internal/config/auth.go | 20 | ||||
-rw-r--r-- | internal/config/config.go | 12 | ||||
-rw-r--r-- | internal/praefect/auth_test.go | 196 | ||||
-rw-r--r-- | internal/praefect/config/config.go | 2 | ||||
-rw-r--r-- | internal/praefect/conn/client_connections.go | 5 | ||||
-rw-r--r-- | internal/praefect/conn/client_connections_test.go | 4 | ||||
-rw-r--r-- | internal/praefect/coordinator_test.go | 2 | ||||
-rw-r--r-- | internal/praefect/replicator_test.go | 6 | ||||
-rw-r--r-- | internal/praefect/server.go | 3 | ||||
-rw-r--r-- | internal/praefect/server_test.go | 24 | ||||
-rw-r--r-- | internal/server/auth/auth.go | 61 | ||||
-rw-r--r-- | internal/server/auth_test.go | 11 | ||||
-rw-r--r-- | internal/server/server.go | 4 | ||||
-rw-r--r-- | internal/service/repository/testhelper_test.go | 29 | ||||
-rw-r--r-- | internal/service/server/info_test.go | 28 |
19 files changed, 346 insertions, 86 deletions
diff --git a/changelogs/unreleased/jc-praefect-auth.yml b/changelogs/unreleased/jc-praefect-auth.yml new file mode 100644 index 000000000..b321ce013 --- /dev/null +++ b/changelogs/unreleased/jc-praefect-auth.yml @@ -0,0 +1,5 @@ +--- +title: Adding auth to praefect +merge_request: 1517 +author: +type: added diff --git a/cmd/praefect/main.go b/cmd/praefect/main.go index a7c9beddf..e5c434ae0 100644 --- a/cmd/praefect/main.go +++ b/cmd/praefect/main.go @@ -96,7 +96,7 @@ func run(listeners []net.Listener, conf config.Config) error { clientConnections := conn.NewClientConnections() for _, node := range conf.Nodes { - if err := clientConnections.RegisterNode(node.Storage, node.Address); err != nil { + if err := clientConnections.RegisterNode(node.Storage, node.Address, node.Token); err != nil { return fmt.Errorf("failed to register %s: %s", node.Address, err) } diff --git a/config.praefect.toml.example b/config.praefect.toml.example index 368f9fed6..74e929f33 100644 --- a/config.praefect.toml.example +++ b/config.praefect.toml.example @@ -7,7 +7,6 @@ listen_addr = "127.0.0.1:2305" # socket_path = "/home/git/gitlab/tmp/sockets/private/praefect.socket" # # Optional: export metrics via Prometheus # prometheus_listen_addr = "127.0.01:10101" - # # You can optionally configure Praefect to output JSON-formatted log messages to stdout # [logging] # format = "json" @@ -15,7 +14,11 @@ listen_addr = "127.0.0.1:2305" # # One of, in order: debug, info, warn, errror, fatal, panic # # Defaults to "info" # level = "warn" - +# +# Optional: authenticate Gitaly requests using a shared secret. This token works the same way as a gitaly token +# [auth] +# token = 'abc123secret' +# # # One or more Gitaly servers need to be configured to be managed. The names # of each server are used to link multiple nodes, or `gitaly_server`s together # as shard. listen_addr should be unique for all nodes. @@ -25,11 +28,15 @@ listen_addr = "127.0.0.1:2305" storage = "praefect-git-0" address = "tcp://praefect-git-0.internal" primary = true + token = 'token1' [[node]] storage = "praefect-git-1" address = "tcp://praefect-git-1.internal" + token = 'token2' [[node]] storage = "praefect-git-2" address = "tcp://praefect-git-2.internal" + token = 'token3' + diff --git a/internal/auth/auth.go b/internal/auth/auth.go new file mode 100644 index 000000000..1bf8de921 --- /dev/null +++ b/internal/auth/auth.go @@ -0,0 +1,7 @@ +package auth + +// Config is a struct for an authentication config +type Config struct { + Transitioning bool `toml:"transitioning"` + Token string `toml:"token"` +} diff --git a/internal/config/auth.go b/internal/config/auth.go deleted file mode 100644 index 98f192769..000000000 --- a/internal/config/auth.go +++ /dev/null @@ -1,20 +0,0 @@ -package config - -import ( - log "github.com/sirupsen/logrus" -) - -// Auth contains the authentication settings for this Gitaly process. -type Auth struct { - Transitioning bool `toml:"transitioning"` - Token string `toml:"token"` -} - -func validateToken() error { - if !Config.Auth.Transitioning || len(Config.Auth.Token) == 0 { - return nil - } - - log.Warn("Authentication is enabled but not enforced because transitioning=true. Gitaly will accept unauthenticated requests.") - return nil -} diff --git a/internal/config/config.go b/internal/config/config.go index 372ab182f..98631a6ac 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -12,6 +12,7 @@ import ( "github.com/BurntSushi/toml" "github.com/kelseyhightower/envconfig" log "github.com/sirupsen/logrus" + "gitlab.com/gitlab-org/gitaly/internal/auth" ) const ( @@ -39,7 +40,7 @@ type Cfg struct { Storages []Storage `toml:"storage" envconfig:"storage"` Logging Logging `toml:"logging" envconfig:"logging"` Prometheus Prometheus `toml:"prometheus"` - Auth Auth `toml:"auth"` + Auth auth.Config `toml:"auth"` TLS TLS `toml:"tls"` Ruby Ruby `toml:"gitaly-ruby"` GitlabShell GitlabShell `toml:"gitlab-shell"` @@ -286,3 +287,12 @@ func validateBinDir() error { Config.BinDir, err = filepath.Abs(Config.BinDir) return err } + +func validateToken() error { + if !Config.Auth.Transitioning || len(Config.Auth.Token) == 0 { + return nil + } + + log.Warn("Authentication is enabled but not enforced because transitioning=true. Gitaly will accept unauthenticated requests.") + return nil +} diff --git a/internal/praefect/auth_test.go b/internal/praefect/auth_test.go new file mode 100644 index 000000000..750515dbd --- /dev/null +++ b/internal/praefect/auth_test.go @@ -0,0 +1,196 @@ +package praefect + +import ( + "context" + "net" + "testing" + + "github.com/golang/protobuf/proto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + gitalyauth "gitlab.com/gitlab-org/gitaly/auth" + "gitlab.com/gitlab-org/gitaly/internal/auth" + "gitlab.com/gitlab-org/gitaly/internal/log" + "gitlab.com/gitlab-org/gitaly/internal/praefect/config" + "gitlab.com/gitlab-org/gitaly/internal/praefect/conn" + "gitlab.com/gitlab-org/gitaly/internal/praefect/mock" + "gitlab.com/gitlab-org/gitaly/internal/praefect/models" + "gitlab.com/gitlab-org/gitaly/internal/praefect/protoregistry" + "gitlab.com/gitlab-org/gitaly/internal/testhelper" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +func TestAuthFailures(t *testing.T) { + ctx, cancel := testhelper.Context() + defer cancel() + + testCases := []struct { + desc string + opts []grpc.DialOption + code codes.Code + }{ + { + desc: "no auth", + opts: nil, + code: codes.Unauthenticated, + }, + { + desc: "invalid auth", + opts: []grpc.DialOption{grpc.WithPerRPCCredentials(brokenAuth{})}, + code: codes.Unauthenticated, + }, + { + desc: "wrong secret", + opts: []grpc.DialOption{grpc.WithPerRPCCredentials(gitalyauth.RPCCredentials("foobar"))}, + code: codes.PermissionDenied, + }, + { + desc: "wrong secret new auth", + opts: []grpc.DialOption{grpc.WithPerRPCCredentials(gitalyauth.RPCCredentialsV2("foobar"))}, + code: codes.PermissionDenied, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + srv, serverSocketPath, cleanup := runServer(t, "quxbaz", true) + defer srv.Shutdown(ctx) + defer cleanup() + + connOpts := append(tc.opts, grpc.WithInsecure()) + conn, err := dial(serverSocketPath, connOpts) + require.NoError(t, err, tc.desc) + defer conn.Close() + + cli := mock.NewSimpleServiceClient(conn) + + _, err = cli.SimpleUnaryUnary(ctx, &mock.SimpleRequest{ + Value: 1, + }) + + testhelper.RequireGrpcError(t, err, tc.code) + }) + } +} + +func TestAuthSuccess(t *testing.T) { + ctx, cancel := testhelper.Context() + defer cancel() + + token := "foobar" + + testCases := []struct { + desc string + opts []grpc.DialOption + required bool + token string + }{ + {desc: "no auth, not required"}, + { + desc: "v1 incorrect auth, not required", + opts: []grpc.DialOption{grpc.WithPerRPCCredentials(gitalyauth.RPCCredentials("incorrect"))}, + token: token, + }, + { + desc: "v1 correct auth, not required", + opts: []grpc.DialOption{grpc.WithPerRPCCredentials(gitalyauth.RPCCredentials(token))}, + token: token, + }, + { + desc: "v1 correct auth, required", + opts: []grpc.DialOption{grpc.WithPerRPCCredentials(gitalyauth.RPCCredentials(token))}, + token: token, + required: true, + }, + { + desc: "v2 correct new auth, not required", + opts: []grpc.DialOption{grpc.WithPerRPCCredentials(gitalyauth.RPCCredentialsV2(token))}, + token: token, + }, + { + desc: "v2 incorrect auth, not required", + opts: []grpc.DialOption{grpc.WithPerRPCCredentials(gitalyauth.RPCCredentialsV2("incorrect"))}, + token: token, + }, + { + desc: "v2 correct new auth, required", + opts: []grpc.DialOption{grpc.WithPerRPCCredentials(gitalyauth.RPCCredentialsV2(token))}, + token: token, + required: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + srv, serverSocketPath, cleanup := runServer(t, tc.token, tc.required) + defer srv.Shutdown(ctx) + defer cleanup() + + connOpts := append(tc.opts, grpc.WithInsecure()) + conn, err := dial(serverSocketPath, connOpts) + require.NoError(t, err, tc.desc) + defer conn.Close() + + cli := mock.NewSimpleServiceClient(conn) + + _, err = cli.SimpleUnaryUnary(ctx, &mock.SimpleRequest{ + Value: 1, + }) + + assert.NoError(t, err, tc.desc) + }) + } +} + +type brokenAuth struct{} + +func (brokenAuth) RequireTransportSecurity() bool { return false } +func (brokenAuth) GetRequestMetadata(context.Context, ...string) (map[string]string, error) { + return map[string]string{"authorization": "Bearer blablabla"}, nil +} + +func dial(serverSocketPath string, opts []grpc.DialOption) (*grpc.ClientConn, error) { + return grpc.Dial(serverSocketPath, opts...) +} + +func runServer(t *testing.T, token string, required bool) (*Server, string, func()) { + backendToken := "abcxyz" + backend, cleanup := newMockDownstream(t, backendToken, callbackIncrement) + + conf := config.Config{ + Auth: auth.Config{Token: token, Transitioning: !required}, + Nodes: []*models.Node{ + &models.Node{ + Storage: "praefect-internal-0", + DefaultPrimary: true, + Address: backend, + }, + }, + } + + gz := proto.FileDescriptor("mock.proto") + fd, err := protoregistry.ExtractFileDescriptor(gz) + if err != nil { + panic(err) + } + + logEntry := log.Default() + datastore := NewMemoryDatastore(conf) + + clientConnections := conn.NewClientConnections() + clientConnections.RegisterNode("praefect-internal-0", backend, backendToken) + + coordinator := NewCoordinator(logEntry, datastore, clientConnections, fd) + + replMgr := NewReplMgr("praefect-internal-0", logEntry, datastore, clientConnections) + + srv := NewServer(coordinator, replMgr, nil, logEntry, clientConnections, conf) + + serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() + + listener, err := net.Listen("unix", serverSocketPath) + require.NoError(t, err) + go srv.Start(listener) + + return srv, "unix://" + serverSocketPath, cleanup +} diff --git a/internal/praefect/config/config.go b/internal/praefect/config/config.go index 633f57d1f..818acae32 100644 --- a/internal/praefect/config/config.go +++ b/internal/praefect/config/config.go @@ -6,6 +6,7 @@ import ( "github.com/BurntSushi/toml" + "gitlab.com/gitlab-org/gitaly/internal/auth" "gitlab.com/gitlab-org/gitaly/internal/config" "gitlab.com/gitlab-org/gitaly/internal/praefect/models" ) @@ -19,6 +20,7 @@ type Config struct { Logging config.Logging `toml:"logging"` PrometheusListenAddr string `toml:"prometheus_listen_addr"` + Auth auth.Config `toml:"auth"` } // FromFile loads the config for the passed file path diff --git a/internal/praefect/conn/client_connections.go b/internal/praefect/conn/client_connections.go index 8d4f3a033..c3734937a 100644 --- a/internal/praefect/conn/client_connections.go +++ b/internal/praefect/conn/client_connections.go @@ -6,7 +6,6 @@ import ( gitalyauth "gitlab.com/gitlab-org/gitaly/auth" "gitlab.com/gitlab-org/gitaly/client" - gitalyconfig "gitlab.com/gitlab-org/gitaly/internal/config" "gitlab.com/gitlab-org/gitaly/internal/praefect/grpc-proxy/proxy" "google.golang.org/grpc" ) @@ -32,11 +31,11 @@ func NewClientConnections() *ClientConnections { // RegisterNode will direct traffic to the supplied downstream connection when the storage location // is encountered. -func (c *ClientConnections) RegisterNode(storageName, listenAddr string) error { +func (c *ClientConnections) RegisterNode(storageName, listenAddr, token string) error { conn, err := client.Dial(listenAddr, []grpc.DialOption{ grpc.WithDefaultCallOptions(grpc.CallCustomCodec(proxy.Codec())), - grpc.WithPerRPCCredentials(gitalyauth.RPCCredentials(gitalyconfig.Config.Auth.Token)), + grpc.WithPerRPCCredentials(gitalyauth.RPCCredentials(token)), }, ) if err != nil { diff --git a/internal/praefect/conn/client_connections_test.go b/internal/praefect/conn/client_connections_test.go index 9e2f832d1..2c5f77a54 100644 --- a/internal/praefect/conn/client_connections_test.go +++ b/internal/praefect/conn/client_connections_test.go @@ -15,12 +15,12 @@ func TestRegisterNode(t *testing.T) { _, err := clientConn.GetConnection(storageName) require.Equal(t, ErrConnectionNotFound, err) - require.NoError(t, clientConn.RegisterNode(storageName, fmt.Sprintf("tcp://%s", tcpAddress))) + require.NoError(t, clientConn.RegisterNode(storageName, fmt.Sprintf("tcp://%s", tcpAddress), "token")) conn, err := clientConn.GetConnection(storageName) require.NoError(t, err) require.Equal(t, tcpAddress, conn.Target()) - err = clientConn.RegisterNode(storageName, "tcp://some-other-address") + err = clientConn.RegisterNode(storageName, "tcp://some-other-address", "token") require.Equal(t, ErrAlreadyRegistered, err) } diff --git a/internal/praefect/coordinator_test.go b/internal/praefect/coordinator_test.go index 267ce9fbb..bcc9d9132 100644 --- a/internal/praefect/coordinator_test.go +++ b/internal/praefect/coordinator_test.go @@ -51,7 +51,7 @@ func TestStreamDirector(t *testing.T) { address := "gitaly-primary.example.com" clientConnections := conn.NewClientConnections() - clientConnections.RegisterNode("praefect-internal-1", fmt.Sprintf("tcp://%s", address)) + clientConnections.RegisterNode("praefect-internal-1", fmt.Sprintf("tcp://%s", address), "token") coordinator := NewCoordinator(log.Default(), datastore, clientConnections) require.NoError(t, coordinator.RegisterProtos(protoregistry.GitalyProtoFileDescriptors...)) diff --git a/internal/praefect/replicator_test.go b/internal/praefect/replicator_test.go index 22b7ed024..a46b2140c 100644 --- a/internal/praefect/replicator_test.go +++ b/internal/praefect/replicator_test.go @@ -98,8 +98,8 @@ func TestProceessReplicationJob(t *testing.T) { replicator.log = gitaly_log.Default() clientCC := conn.NewClientConnections() - clientCC.RegisterNode("default", srvSocketPath) - clientCC.RegisterNode("backup", srvSocketPath) + clientCC.RegisterNode("default", srvSocketPath, gitaly_config.Config.Auth.Token) + clientCC.RegisterNode("backup", srvSocketPath, gitaly_config.Config.Auth.Token) replMgr := &ReplMgr{ log: gitaly_log.Default(), @@ -178,7 +178,7 @@ func TestMain(m *testing.M) { func testMain(m *testing.M) int { defer testhelper.MustHaveNoChildProcess() - gitaly_config.Config.Auth = gitaly_config.Auth{Token: testhelper.RepositoryAuthToken} + gitaly_config.Config.Auth.Token = testhelper.RepositoryAuthToken var err error gitaly_config.Config.GitlabShell.Dir, err = filepath.Abs("testdata/gitlab-shell") diff --git a/internal/praefect/server.go b/internal/praefect/server.go index 7e2307e62..d91385d05 100644 --- a/internal/praefect/server.go +++ b/internal/praefect/server.go @@ -16,6 +16,7 @@ import ( "gitlab.com/gitlab-org/gitaly/internal/praefect/conn" "gitlab.com/gitlab-org/gitaly/internal/praefect/grpc-proxy/proxy" "gitlab.com/gitlab-org/gitaly/internal/praefect/service/server" + "gitlab.com/gitlab-org/gitaly/internal/server/auth" "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc" grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc" @@ -40,6 +41,7 @@ func NewServer(c *Coordinator, repl ReplMgr, grpcOpts []grpc.ServerOption, l *lo grpc_prometheus.StreamServerInterceptor, cancelhandler.Stream, // Should be below LogHandler grpctracing.StreamServerTracingInterceptor(), + auth.StreamServerInterceptor(conf.Auth), // Panic handler should remain last so that application panics will be // converted to errors and logged panichandler.StreamPanicHandler, @@ -50,6 +52,7 @@ func NewServer(c *Coordinator, repl ReplMgr, grpcOpts []grpc.ServerOption, l *lo grpc_prometheus.UnaryServerInterceptor, cancelhandler.Unary, // Should be below LogHandler grpctracing.UnaryServerTracingInterceptor(), + auth.UnaryServerInterceptor(conf.Auth), // Panic handler should remain last so that application panics will be // converted to errors and logged panichandler.UnaryPanicHandler, diff --git a/internal/praefect/server_test.go b/internal/praefect/server_test.go index 6545f3cdf..b7ce6e1b4 100644 --- a/internal/praefect/server_test.go +++ b/internal/praefect/server_test.go @@ -10,6 +10,7 @@ import ( "github.com/golang/protobuf/proto" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/gitaly/client" + internalauth "gitlab.com/gitlab-org/gitaly/internal/auth" "gitlab.com/gitlab-org/gitaly/internal/log" "gitlab.com/gitlab-org/gitaly/internal/praefect/config" "gitlab.com/gitlab-org/gitaly/internal/praefect/conn" @@ -68,10 +69,12 @@ func TestServerSimpleUnaryUnary(t *testing.T) { ID: 1, Storage: "praefect-internal-1", DefaultPrimary: true, + Token: "abc", }, &models.Node{ ID: 2, Storage: "praefect-internal-2", + Token: "xyz", }}, } @@ -82,10 +85,10 @@ func TestServerSimpleUnaryUnary(t *testing.T) { clientCC := conn.NewClientConnections() for id, nodeStorage := range datastore.storageNodes.m { - backend, cleanup := newMockDownstream(t, tt.callback) + backend, cleanup := newMockDownstream(t, nodeStorage.Token, tt.callback) defer cleanup() // clean up mock downstream server resources - clientCC.RegisterNode(nodeStorage.Storage, backend) + clientCC.RegisterNode(nodeStorage.Storage, backend, nodeStorage.Token) nodeStorage.Address = backend datastore.storageNodes.m[id] = nodeStorage } @@ -145,10 +148,12 @@ func TestGitalyServerInfo(t *testing.T) { ID: 1, Storage: "praefect-internal-1", DefaultPrimary: true, + Token: "abc", }, &models.Node{ ID: 2, Storage: "praefect-internal-2", + Token: "xyz", }}, } cc, srv := runFullPraefectServer(t, conf) @@ -175,9 +180,9 @@ func runFullPraefectServer(t *testing.T, conf config.Config) (*grpc.ClientConn, clientCC := conn.NewClientConnections() for id, nodeStorage := range datastore.storageNodes.m { - _, backend := runInternalGitalyServer(t) + _, backend := runInternalGitalyServer(t, nodeStorage.Token) - clientCC.RegisterNode(nodeStorage.Storage, backend) + clientCC.RegisterNode(nodeStorage.Storage, backend, nodeStorage.Token) nodeStorage.Address = backend datastore.storageNodes.m[id] = nodeStorage } @@ -215,9 +220,9 @@ func runFullPraefectServer(t *testing.T, conf config.Config) (*grpc.ClientConn, return cc, prf } -func runInternalGitalyServer(t *testing.T) (*grpc.Server, string) { - streamInt := []grpc.StreamServerInterceptor{auth.StreamServerInterceptor()} - unaryInt := []grpc.UnaryServerInterceptor{auth.UnaryServerInterceptor()} +func runInternalGitalyServer(t *testing.T, token string) (*grpc.Server, string) { + streamInt := []grpc.StreamServerInterceptor{auth.StreamServerInterceptor(internalauth.Config{Token: token})} + unaryInt := []grpc.UnaryServerInterceptor{auth.UnaryServerInterceptor(internalauth.Config{Token: token})} server := testhelper.NewTestGrpcServer(t, streamInt, unaryInt) serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() @@ -268,13 +273,14 @@ func dialLocalPort(tb testing.TB, port int, backend bool) *grpc.ClientConn { } // initializes and returns a client to downstream server, downstream server, and cleanup function -func newMockDownstream(tb testing.TB, callback simpleUnaryUnaryCallback) (string, func()) { +func newMockDownstream(tb testing.TB, token string, callback simpleUnaryUnaryCallback) (string, func()) { // setup mock server m := &mockSvc{ simpleUnaryUnary: callback, } - srv := grpc.NewServer() + srv := grpc.NewServer(grpc.UnaryInterceptor(auth.UnaryServerInterceptor(internalauth.Config{Token: token}))) + mock.RegisterSimpleServiceServer(srv, m) // client to backend service diff --git a/internal/server/auth/auth.go b/internal/server/auth/auth.go index 0ac688ba8..8ad2963d8 100644 --- a/internal/server/auth/auth.go +++ b/internal/server/auth/auth.go @@ -7,7 +7,7 @@ import ( grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth" "github.com/prometheus/client_golang/prometheus" gitalyauth "gitlab.com/gitlab-org/gitaly/auth" - "gitlab.com/gitlab-org/gitaly/internal/config" + internalauth "gitlab.com/gitlab-org/gitaly/internal/auth" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -28,45 +28,44 @@ func init() { } // StreamServerInterceptor checks for Gitaly bearer tokens. -func StreamServerInterceptor() grpc.StreamServerInterceptor { - return grpc_auth.StreamServerInterceptor(check) +func StreamServerInterceptor(conf internalauth.Config) grpc.StreamServerInterceptor { + return grpc_auth.StreamServerInterceptor(checkFunc(conf)) } // UnaryServerInterceptor checks for Gitaly bearer tokens. -func UnaryServerInterceptor() grpc.UnaryServerInterceptor { - return grpc_auth.UnaryServerInterceptor(check) +func UnaryServerInterceptor(conf internalauth.Config) grpc.UnaryServerInterceptor { + return grpc_auth.UnaryServerInterceptor(checkFunc(conf)) } -func check(ctx context.Context) (context.Context, error) { - if len(config.Config.Auth.Token) == 0 { - countStatus("server disabled authentication").Inc() - return ctx, nil - } +func checkFunc(conf internalauth.Config) func(ctx context.Context) (context.Context, error) { + return func(ctx context.Context) (context.Context, error) { + if len(conf.Token) == 0 { + countStatus("server disabled authentication", conf.Transitioning).Inc() + return ctx, nil + } - err := gitalyauth.CheckToken(ctx, config.Config.Auth.Token, time.Now()) - switch status.Code(err) { - case codes.OK: - countStatus(okLabel()).Inc() - case codes.Unauthenticated: - countStatus("unauthenticated").Inc() - case codes.PermissionDenied: - countStatus("denied").Inc() - default: - countStatus("invalid").Inc() - } + err := gitalyauth.CheckToken(ctx, conf.Token, time.Now()) + switch status.Code(err) { + case codes.OK: + countStatus(okLabel(conf.Transitioning), conf.Transitioning).Inc() + case codes.Unauthenticated: + countStatus("unauthenticated", conf.Transitioning).Inc() + case codes.PermissionDenied: + countStatus("denied", conf.Transitioning).Inc() + default: + countStatus("invalid", conf.Transitioning).Inc() + } - return ctx, ifEnforced(err) -} + if conf.Transitioning { + err = nil + } -func ifEnforced(err error) error { - if config.Config.Auth.Transitioning { - return nil + return ctx, err } - return err } -func okLabel() string { - if config.Config.Auth.Transitioning { +func okLabel(transitioning bool) string { + if transitioning { // This special value is an extra warning sign to administrators that // authentication is currently not enforced. return "would be ok" @@ -74,9 +73,9 @@ func okLabel() string { return "ok" } -func countStatus(status string) prometheus.Counter { +func countStatus(status string, transitioning bool) prometheus.Counter { enforced := "true" - if config.Config.Auth.Transitioning { + if transitioning { enforced = "false" } return authCount.WithLabelValues(enforced, status) diff --git a/internal/server/auth_test.go b/internal/server/auth_test.go index fdbf3d6b0..462f5d80d 100644 --- a/internal/server/auth_test.go +++ b/internal/server/auth_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" gitalyauth "gitlab.com/gitlab-org/gitaly/auth" + "gitlab.com/gitlab-org/gitaly/internal/auth" "gitlab.com/gitlab-org/gitaly/internal/config" "gitlab.com/gitlab-org/gitaly/internal/testhelper" "google.golang.org/grpc" @@ -58,7 +59,7 @@ func TestTLSSanity(t *testing.T) { } func TestAuthFailures(t *testing.T) { - defer func(oldAuth config.Auth) { + defer func(oldAuth auth.Config) { config.Config.Auth = oldAuth }(config.Config.Auth) config.Config.Auth.Token = "quxbaz" @@ -100,7 +101,7 @@ func TestAuthFailures(t *testing.T) { } func TestAuthSuccess(t *testing.T) { - defer func(oldAuth config.Auth) { + defer func(oldAuth auth.Config) { config.Config.Auth = oldAuth }(config.Config.Auth) @@ -148,10 +149,8 @@ func TestAuthSuccess(t *testing.T) { } for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - config.Config.Auth = config.Auth{ - Transitioning: !tc.required, - Token: tc.token, - } + config.Config.Auth.Token = tc.token + config.Config.Auth.Transitioning = !tc.required srv, serverSocketPath := runServer(t) defer srv.Stop() diff --git a/internal/server/server.go b/internal/server/server.go index 8be44a7ae..97013d48e 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -85,7 +85,7 @@ func createNewServer(rubyServer *rubyserver.Server, secure bool) *grpc.Server { sentryhandler.StreamLogHandler, cancelhandler.Stream, // Should be below LogHandler lh.StreamInterceptor(), - auth.StreamServerInterceptor(), + auth.StreamServerInterceptor(config.Config.Auth), grpctracing.StreamServerTracingInterceptor(), cache.StreamInvalidator(diskcache.LeaseKeyer{}, protoregistry.GitalyProtoPreregistered), // Panic handler should remain last so that application panics will be @@ -101,7 +101,7 @@ func createNewServer(rubyServer *rubyserver.Server, secure bool) *grpc.Server { sentryhandler.UnaryLogHandler, cancelhandler.Unary, // Should be below LogHandler lh.UnaryInterceptor(), - auth.UnaryServerInterceptor(), + auth.UnaryServerInterceptor(config.Config.Auth), grpctracing.UnaryServerTracingInterceptor(), cache.UnaryInvalidator(diskcache.LeaseKeyer{}, protoregistry.GitalyProtoPreregistered), // Panic handler should remain last so that application panics will be diff --git a/internal/service/repository/testhelper_test.go b/internal/service/repository/testhelper_test.go index 245310ca6..e9be51311 100644 --- a/internal/service/repository/testhelper_test.go +++ b/internal/service/repository/testhelper_test.go @@ -16,6 +16,7 @@ import ( "gitlab.com/gitlab-org/gitaly/internal/testhelper" "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/reflection" ) @@ -43,8 +44,8 @@ func newRepositoryClient(t *testing.T, serverSocketPath string) (gitalypb.Reposi var NewRepositoryClient = newRepositoryClient func runRepoServer(t *testing.T) (*grpc.Server, string) { - streamInt := []grpc.StreamServerInterceptor{auth.StreamServerInterceptor()} - unaryInt := []grpc.UnaryServerInterceptor{auth.UnaryServerInterceptor()} + streamInt := []grpc.StreamServerInterceptor{auth.StreamServerInterceptor(config.Config.Auth)} + unaryInt := []grpc.UnaryServerInterceptor{auth.UnaryServerInterceptor(config.Config.Auth)} server := testhelper.NewTestGrpcServer(t, streamInt, unaryInt) serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() @@ -62,6 +63,28 @@ func runRepoServer(t *testing.T) (*grpc.Server, string) { return server, "unix://" + serverSocketPath } +func TestRepoNoAuth(t *testing.T) { + srv, path := runRepoServer(t) + defer srv.Stop() + + connOpts := []grpc.DialOption{ + grpc.WithInsecure(), + } + + conn, err := grpc.Dial(path, connOpts...) + if err != nil { + t.Fatal(err) + } + + ctx, cancel := testhelper.Context() + defer cancel() + + client := gitalypb.NewRepositoryServiceClient(conn) + _, err = client.CreateRepository(ctx, &gitalypb.CreateRepositoryRequest{Repository: &gitalypb.Repository{StorageName: "default", RelativePath: "new/project/path"}}) + + testhelper.RequireGrpcError(t, err, codes.Unauthenticated) +} + func assertModTimeAfter(t *testing.T, afterTime time.Time, paths ...string) bool { // NOTE: Since some filesystems don't have sub-second precision on `mtime` // we're rounding the times to seconds @@ -84,7 +107,7 @@ func TestMain(m *testing.M) { func testMain(m *testing.M) int { defer testhelper.MustHaveNoChildProcess() - config.Config.Auth = config.Auth{Token: testhelper.RepositoryAuthToken} + config.Config.Auth.Token = testhelper.RepositoryAuthToken var err error config.Config.GitlabShell.Dir, err = filepath.Abs("testdata/gitlab-shell") diff --git a/internal/service/server/info_test.go b/internal/service/server/info_test.go index d1b287285..d708c8a03 100644 --- a/internal/service/server/info_test.go +++ b/internal/service/server/info_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" gitalyauth "gitlab.com/gitlab-org/gitaly/auth" + internalauth "gitlab.com/gitlab-org/gitaly/internal/auth" "gitlab.com/gitlab-org/gitaly/internal/config" "gitlab.com/gitlab-org/gitaly/internal/git" "gitlab.com/gitlab-org/gitaly/internal/server/auth" @@ -15,6 +16,7 @@ import ( "gitlab.com/gitlab-org/gitaly/internal/version" "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/reflection" ) @@ -67,8 +69,9 @@ func TestGitalyServerInfo(t *testing.T) { } func runServer(t *testing.T) (*grpc.Server, string) { - streamInt := []grpc.StreamServerInterceptor{auth.StreamServerInterceptor()} - unaryInt := []grpc.UnaryServerInterceptor{auth.UnaryServerInterceptor()} + authConfig := internalauth.Config{Token: testhelper.RepositoryAuthToken} + streamInt := []grpc.StreamServerInterceptor{auth.StreamServerInterceptor(authConfig)} + unaryInt := []grpc.UnaryServerInterceptor{auth.UnaryServerInterceptor(authConfig)} server := testhelper.NewTestGrpcServer(t, streamInt, unaryInt) serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() @@ -85,6 +88,27 @@ func runServer(t *testing.T) (*grpc.Server, string) { return server, "unix://" + serverSocketPath } +func TestServerNoAuth(t *testing.T) { + srv, path := runServer(t) + defer srv.Stop() + + connOpts := []grpc.DialOption{ + grpc.WithInsecure(), + } + + conn, err := grpc.Dial(path, connOpts...) + if err != nil { + t.Fatal(err) + } + + ctx, cancel := testhelper.Context() + defer cancel() + + client := gitalypb.NewServerServiceClient(conn) + _, err = client.ServerInfo(ctx, &gitalypb.ServerInfoRequest{}) + + testhelper.RequireGrpcError(t, err, codes.Unauthenticated) +} func newServerClient(t *testing.T, serverSocketPath string) (gitalypb.ServerServiceClient, *grpc.ClientConn) { connOpts := []grpc.DialOption{ |