From 2f4e5edc13dd2a2082c4354821efca9bb193b40c Mon Sep 17 00:00:00 2001 From: Dries Mys Date: Sun, 21 Apr 2024 17:52:46 +0200 Subject: [PATCH] Moved NTLM to rdpgw-auth to avoid accessing passwords in main program NTLM tests added NTLM licensing info added Avoid logging NTLM messages as it may contain sensitive information Renamed database authentication to NTLM as requested by bolkedebruin (see PR #109) --- README.md | 47 ++-- cmd/auth/auth.go | 43 +++- cmd/auth/config/configuration.go | 42 ++++ cmd/{rdpgw => auth}/database/config.go | 4 +- cmd/auth/database/config_test.go | 43 ++++ cmd/{rdpgw => auth}/database/database.go | 0 cmd/auth/ntlm/ntlm.go | 160 ++++++++++++++ cmd/auth/ntlm/ntlm_test.go | 168 ++++++++++++++ cmd/rdpgw/config/configuration.go | 19 +- cmd/rdpgw/main.go | 34 +-- cmd/rdpgw/web/basic.go | 16 -- cmd/rdpgw/web/ntlm.go | 268 +++++++---------------- proto/auth.proto | 14 +- shared/auth/auth.pb.go | 197 +++++++++++++++-- shared/auth/auth_grpc.pb.go | 46 +++- 15 files changed, 817 insertions(+), 284 deletions(-) create mode 100644 cmd/auth/config/configuration.go rename cmd/{rdpgw => auth}/database/config.go (84%) create mode 100644 cmd/auth/database/config_test.go rename cmd/{rdpgw => auth}/database/database.go (100%) create mode 100644 cmd/auth/ntlm/ntlm.go create mode 100644 cmd/auth/ntlm/ntlm_test.go diff --git a/README.md b/README.md index c5b7f28..7721438 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,8 @@ If local authentication is used the client will need to provide a username and p against PAM. This requires, to ensure privilege separation, that ```rdpgw-auth``` is also running and a valid PAM configuration is provided per typical configuration. -If database authentication is used, the allowed user credentials for the gateway should be configured in the -configuration file. +If NTLM authentication is used, the allowed user credentials for the gateway should be configured in the +configuration file of `rdpgw-auth`. Finally, RDP hosts that the client wants to connect to are verified against what was provided by / allowed by the server. Next to that the client's ip address needs to match the one it obtained the gateway token with if @@ -61,7 +61,7 @@ settings. ## Authentication RDPGW wants to be secure when you set it up from the start. It supports several authentication -mechanisms such as OpenID Connect, Kerberos, PAM or a database. +mechanisms such as OpenID Connect, Kerberos, PAM or NTLM. Technically, cookies are encrypted and signed on the client side relying on [Gorilla Sessions](https://www.gorillatoolkit.org/pkg/sessions). PAA tokens (gateway access tokens) @@ -75,7 +75,7 @@ if you want. ### Mixing authentication mechanisms -It is technically possible to mix authentication mechanisms. Currently, you can mix local with Kerberos or database. If you enable +It is technically possible to mix authentication mechanisms. Currently, you can mix local with Kerberos or NTLM. If you enable OpenID Connect it is not possible to mix it with local or Kerberos at the moment. ### Open ID Connect @@ -149,7 +149,7 @@ but it also supports LDAP authentication or even Active Directory if you have th `rdpgw-auth` that is used to authenticate the user. This program needs to be run as root or setuid. __NOTE__: The default windows client ``mstsc`` does not support basic auth. You will need to use a different client or -switch to OpenID Connect, Kerberos or database authentication. +switch to OpenID Connect, Kerberos or NTLM authentication. __NOTE__: Using PAM for passwd (i.e. LDAP is fine) within a container is not recommended. It is better to use OpenID Connect or Kerberos. If you do want to use it within a container you can choose to run the helper program outside the @@ -183,30 +183,35 @@ Make sure to run both the gateway and `rdpgw-auth`. The gateway will connect to The client can then connect to the gateway directly by using a remote desktop client. -### Database (Basic Auth or NTLM) +### NTLM -The gateway can also support authentication using a local database. -Currently, only the configuration file is supported as a database. +The gateway can also support NTLM authentication. +Currently, only the configuration file is supported as a database for credential lookup. In the future, support for real databases (e.g. sqlite) may be added. -Database authentication has the advantage that it is easy to setup, especially in case the gateway is used for a limited number of users. -Unlike PAM / local, database authentication supports the default windows client ``mstsc``. +NTLM authentication has the advantage that it is easy to setup, especially in case the gateway is used for a limited number of users. +Unlike PAM / local, NTLM authentication supports the default windows client ``mstsc``. __WARNING__: The password is currently saved in plain text. So, you should keep the config file as secure as possible and avoid reusing the same password for other applications. The password is stored in plain text to support the NTLM authentication protocol. -To enable database authentication make sure to set the following variables in the configuration file. +To enable NTLM authentication make sure to set the following variables in the configuration file. +Configuration file for `rdpgw`: ```yaml Server: Authentication: - - database -Users: - - {Username: "username1", Password: "secure_password"} # Modify this password! + - ntlm Caps: TokenAuth: false ``` +Configuration file for `rdpgw-auth`: +````yaml +Users: + - {Username: "my_username", Password: "my_secure_password"} # Modify this password! +```` + The client can then connect to the gateway directly by using a remote desktop client using the gateway credentials configured in the YAML configuration file. @@ -239,17 +244,17 @@ TLS termination. ```yaml # web server configuration. Server: - # can be set to openid, kerberos, local and database. If openid is used rdpgw expects + # can be set to openid, kerberos, local and ntlm. If openid is used rdpgw expects # a configured openid provider, make sure to set caps.tokenauth to true. If local # rdpgw connects to rdpgw-auth over a socket to verify users and password. Note: # rdpgw-auth needs to be run as root or setuid in order to work. If kerberos is # used a keytab and krb5conf need to be supplied. local can be stacked with - # kerberos or database authentication, so that the clients selects what it wants. + # kerberos or ntlm authentication, so that the clients selects what it wants. Authentication: # - kerberos # - local - openid - # - database + # - ntlm # The socket to connect to if using local auth. Ensure rdpgw auth is configured to # use the same socket. # AuthSocket: /tmp/rdpgw-auth.sock @@ -298,9 +303,6 @@ OpenId: # Keytab: /etc/keytabs/rdpgw.keytab # Krb5conf: /etc/krb5.conf # enabled / disabled capabilities -# Users: -# - {Username: "username1", Password: "secure_password"} -# - {Username: "username2", Password: "secure_password2"} Caps: SmartCardAuth: false # required for openid connect @@ -404,7 +406,7 @@ In this way you can integrate, for example, it with [pam-jwt](https://github.com The several clients that Microsoft provides come with their own caveats. The most important one is that the default client on Windows ``mstsc`` does not support basic authentication. This means you need to use either OpenID Connect, -Kerberos or database authentication. +Kerberos or ntlm authentication. In addition to that, ``mstsc``, when configuring a gateway directly in the client requires you to either: @@ -430,4 +432,5 @@ flexibility. ## TODO * Improve Web Interface - +# Acknowledgements +* This product includes software developed by the Thomson Reuters Global Resources. ([go-ntlm](https://github.com/m7913d/go-ntlm) - BSD-4 License) diff --git a/cmd/auth/auth.go b/cmd/auth/auth.go index 0fe6aba..ede837a 100644 --- a/cmd/auth/auth.go +++ b/cmd/auth/auth.go @@ -4,6 +4,9 @@ import ( "context" "errors" "fmt" + "github.com/bolkedebruin/rdpgw/cmd/auth/config" + "github.com/bolkedebruin/rdpgw/cmd/auth/database" + "github.com/bolkedebruin/rdpgw/cmd/auth/ntlm" "github.com/bolkedebruin/rdpgw/shared/auth" "github.com/msteinert/pam/v2" "github.com/thought-machine/go-flags" @@ -21,16 +24,24 @@ const ( var opts struct { ServiceName string `short:"n" long:"name" default:"rdpgw" description:"the PAM service name to use"` SocketAddr string `short:"s" long:"socket" default:"/tmp/rdpgw-auth.sock" description:"the location of the socket"` + ConfigFile string `short:"c" long:"conf" default:"rdpgw-auth.yaml" description:"users config file for NTLM (yaml)"` } type AuthServiceImpl struct { + auth.UnimplementedAuthenticateServer + serviceName string + ntlm *ntlm.NTLMAuth } +var conf config.Configuration var _ auth.AuthenticateServer = (*AuthServiceImpl)(nil) -func NewAuthService(serviceName string) auth.AuthenticateServer { - s := &AuthServiceImpl{serviceName: serviceName} +func NewAuthService(serviceName string, database database.Database) auth.AuthenticateServer { + s := &AuthServiceImpl{ + serviceName: serviceName, + ntlm: ntlm.NewNTLMAuth(database), + } return s } @@ -77,12 +88,35 @@ func (s *AuthServiceImpl) Authenticate(ctx context.Context, message *auth.UserPa return r, nil } +func (s *AuthServiceImpl) NTLM(ctx context.Context, message *auth.NtlmRequest) (*auth.NtlmResponse, error) { + r, err := s.ntlm.Authenticate(message) + + if err != nil { + log.Printf("[%s] NTLM failed: %s", message.Session, err) + } else if r.Authenticated { + log.Printf("[%s] User: %s authenticated using NTLM", message.Session, r.Username) + } else if r.NtlmMessage != "" { + log.Printf("[%s] Sending NTLM challenge", message.Session) + } + + return r, err +} + func main() { _, err := flags.Parse(&opts) if err != nil { - panic(err) + var fErr *flags.Error + if errors.As(err, &fErr) { + if fErr.Type == flags.ErrHelp { + fmt.Printf("Acknowledgements:\n") + fmt.Printf(" - This product includes software developed by the Thomson Reuters Global Resources. (go-ntlm - https://github.com/m7913d/go-ntlm - BSD-4 License)\n") + } + } + return } + conf = config.Load(opts.ConfigFile) + log.Printf("Starting auth server on %s", opts.SocketAddr) cleanup := func() { if _, err := os.Stat(opts.SocketAddr); err == nil { @@ -100,7 +134,8 @@ func main() { log.Fatal(err) } server := grpc.NewServer() - service := NewAuthService(opts.ServiceName) + db := database.NewConfig(conf.Users) + service := NewAuthService(opts.ServiceName, db) auth.RegisterAuthenticateServer(server, service) server.Serve(listener) } diff --git a/cmd/auth/config/configuration.go b/cmd/auth/config/configuration.go new file mode 100644 index 0000000..580b7f2 --- /dev/null +++ b/cmd/auth/config/configuration.go @@ -0,0 +1,42 @@ +package config + +import ( + "github.com/knadh/koanf/parsers/yaml" + "github.com/knadh/koanf/providers/confmap" + "github.com/knadh/koanf/providers/file" + "github.com/knadh/koanf/v2" + "log" + "os" +) + +type Configuration struct { + Users []UserConfig `koanf:"users"` +} + +type UserConfig struct { + Username string `koanf:"username"` + Password string `koanf:"password"` +} + +var Conf Configuration + +func Load(configFile string) Configuration { + + var k = koanf.New(".") + + k.Load(confmap.Provider(map[string]interface{}{}, "."), nil) + + if _, err := os.Stat(configFile); os.IsNotExist(err) { + log.Printf("Config file %s not found, skipping config file", configFile) + } else { + if err := k.Load(file.Provider(configFile), yaml.Parser()); err != nil { + log.Fatalf("Error loading config from file: %v", err) + } + } + + koanfTag := koanf.UnmarshalConf{Tag: "koanf"} + k.UnmarshalWithConf("Users", &Conf.Users, koanfTag) + + return Conf + +} diff --git a/cmd/rdpgw/database/config.go b/cmd/auth/database/config.go similarity index 84% rename from cmd/rdpgw/database/config.go rename to cmd/auth/database/config.go index 37b475c..9ffe5ec 100755 --- a/cmd/rdpgw/database/config.go +++ b/cmd/auth/database/config.go @@ -1,7 +1,7 @@ package database import ( - "github.com/bolkedebruin/rdpgw/cmd/rdpgw/config" + "github.com/bolkedebruin/rdpgw/cmd/auth/config" ) type Config struct { @@ -22,4 +22,4 @@ func NewConfig(users []config.UserConfig) *Config { func (c *Config) GetPassword (username string) string { return c.users[username].Password -} \ No newline at end of file +} diff --git a/cmd/auth/database/config_test.go b/cmd/auth/database/config_test.go new file mode 100644 index 0000000..d13ef75 --- /dev/null +++ b/cmd/auth/database/config_test.go @@ -0,0 +1,43 @@ +package database + +import ( + "github.com/bolkedebruin/rdpgw/cmd/auth/config" + "testing" +) + +func createTestDatabase () (Database) { + var users = []config.UserConfig{} + + user1 := config.UserConfig{} + user1.Username = "my_username" + user1.Password = "my_password" + users = append(users, user1) + + user2 := config.UserConfig{} + user2.Username = "my_username2" + user2.Password = "my_password2" + users = append(users, user2) + + config := NewConfig(users) + + return config +} + +func TestDatabaseConfigValidUsername(t *testing.T) { + database := createTestDatabase() + + if database.GetPassword("my_username") != "my_password" { + t.Fatalf("Wrong password returned") + } + if database.GetPassword("my_username2") != "my_password2" { + t.Fatalf("Wrong password returned") + } +} + +func TestDatabaseInvalidUsername(t *testing.T) { + database := createTestDatabase() + + if database.GetPassword("my_invalid_username") != "" { + t.Fatalf("Non empty password returned for invalid username") + } +} diff --git a/cmd/rdpgw/database/database.go b/cmd/auth/database/database.go similarity index 100% rename from cmd/rdpgw/database/database.go rename to cmd/auth/database/database.go diff --git a/cmd/auth/ntlm/ntlm.go b/cmd/auth/ntlm/ntlm.go new file mode 100644 index 0000000..2dd5dd3 --- /dev/null +++ b/cmd/auth/ntlm/ntlm.go @@ -0,0 +1,160 @@ +package ntlm + +import ( + "encoding/base64" + "errors" + "github.com/bolkedebruin/rdpgw/cmd/auth/database" + "github.com/bolkedebruin/rdpgw/shared/auth" + "github.com/patrickmn/go-cache" + "github.com/m7913d/go-ntlm/ntlm" + "fmt" + "log" + "time" +) + +const ( + cacheExpiration = time.Minute + cleanupInterval = time.Minute * 5 +) + +type NTLMAuth struct { + contextCache *cache.Cache + + // Information about the server, returned to the client during authentication + ServerName string // e.g. EXAMPLE1 + DomainName string // e.g. EXAMPLE + DnsServerName string // e.g. example1.example.com + DnsDomainName string // e.g. example.com + DnsTreeName string // e.g. example.com + + Database database.Database +} + +func NewNTLMAuth (database database.Database) (*NTLMAuth) { + return &NTLMAuth{ + contextCache: cache.New(cacheExpiration, cleanupInterval), + Database: database, + } +} + +func (h *NTLMAuth) Authenticate(message *auth.NtlmRequest) (*auth.NtlmResponse, error) { + r := &auth.NtlmResponse{} + r.Authenticated = false + + if message.Session == "" { + return r, errors.New("Invalid (empty) session specified") + } + + if message.NtlmMessage == "" { + return r, errors.New("Empty NTLM message specified") + } + + c := h.getContext(message.Session) + err := c.Authenticate(message.NtlmMessage, r) + + if err != nil || r.Authenticated { + h.removeContext(message.Session) + } + + return r, err +} + +func (h *NTLMAuth) getContext (session string) (*ntlmContext) { + if c_, found := h.contextCache.Get(session); found { + if c, ok := c_.(*ntlmContext); ok { + return c + } + } + c := new(ntlmContext) + c.h = h + h.contextCache.Set(session, c, cache.DefaultExpiration) + return c +} + +func (h *NTLMAuth) removeContext (session string) { + h.contextCache.Delete(session) +} + +type ntlmContext struct { + session ntlm.ServerSession + h *NTLMAuth +} + +func (c *ntlmContext) Authenticate(authorisationEncoded string, r *auth.NtlmResponse) (error) { + authorisation, err := base64.StdEncoding.DecodeString(authorisationEncoded) + if err != nil { + return errors.New(fmt.Sprintf("Failed to decode NTLM Authorisation header: %s", err)) + } + + nm, err := ntlm.ParseNegotiateMessage(authorisation) + if err == nil { + return c.negotiate(nm, r) + } + if (nm != nil && nm.MessageType == 1) { + return errors.New(fmt.Sprintf("Failed to parse NTLM Authorisation header: %s", err)) + } else if c.session == nil { + return errors.New(fmt.Sprintf("New NTLM auth sequence should start with negotioate request")) + } + + am, err := ntlm.ParseAuthenticateMessage(authorisation, 2) + if err == nil { + return c.authenticate(am, r) + } + + return errors.New(fmt.Sprintf("Failed to parse NTLM Authorisation header: %s", err)) +} + +func (c *ntlmContext) negotiate(nm *ntlm.NegotiateMessage, r *auth.NtlmResponse) (error) { + session, err := ntlm.CreateServerSession(ntlm.Version2, ntlm.ConnectionOrientedMode) + + if err != nil { + c.session = nil; + return errors.New(fmt.Sprintf("Failed to create NTLM server session: %s", err)) + } + + c.session = session + c.session.SetRequireNtHash(true) + c.session.SetDomainName(c.h.DomainName) + c.session.SetComputerName(c.h.ServerName) + c.session.SetDnsDomainName(c.h.DnsDomainName) + c.session.SetDnsComputerName(c.h.DnsServerName) + c.session.SetDnsTreeName(c.h.DnsTreeName) + + err = c.session.ProcessNegotiateMessage(nm) + if err != nil { + return errors.New(fmt.Sprintf("Failed to process NTLM negotiate message: %s", err)) + } + + cm, err := c.session.GenerateChallengeMessage() + if err != nil { + return errors.New(fmt.Sprintf("Failed to generate NTLM challenge message: %s", err)) + } + + r.NtlmMessage = base64.StdEncoding.EncodeToString(cm.Bytes()) + return nil +} + +func (c *ntlmContext) authenticate(am *ntlm.AuthenticateMessage, r *auth.NtlmResponse) (error) { + if c.session == nil { + return errors.New(fmt.Sprintf("NTLM Authenticate requires active session: first call negotioate")) + } + + username := am.UserName.String() + password := c.h.Database.GetPassword (username) + if password == "" { + log.Printf("NTLM: unknown username specified: %s", username) + return nil + } + + c.session.SetUserInfo(username,password,"") + + err := c.session.ProcessAuthenticateMessage(am) + if err != nil { + log.Printf("Failed to process NTLM authenticate message: %s", err) + return nil + } + + r.Authenticated = true + r.Username = username + return nil +} diff --git a/cmd/auth/ntlm/ntlm_test.go b/cmd/auth/ntlm/ntlm_test.go new file mode 100644 index 0000000..1952dd2 --- /dev/null +++ b/cmd/auth/ntlm/ntlm_test.go @@ -0,0 +1,168 @@ +package ntlm + +import ( + "encoding/base64" + "github.com/bolkedebruin/rdpgw/cmd/auth/config" + "github.com/bolkedebruin/rdpgw/cmd/auth/database" + "github.com/bolkedebruin/rdpgw/shared/auth" + "github.com/m7913d/go-ntlm/ntlm" + "testing" + "log" +) + +func createTestDatabase () (database.Database) { + user := config.UserConfig{} + user.Username = "my_username" + user.Password = "my_password" + + var users = []config.UserConfig{} + users = append(users, user) + + config := database.NewConfig(users) + + return config +} + +func TestNtlmValidCredentials(t *testing.T) { + client := ntlm.V2ClientSession{} + client.SetUserInfo("my_username", "my_password", "") + + authenticateResponse := authenticate(t, &client) + if !authenticateResponse.Authenticated { + t.Errorf("Failed to authenticate") + return + } + if authenticateResponse.Username != "my_username" { + t.Errorf("Wrong username returned") + return + } +} + +func TestNtlmInvalidPassword(t *testing.T) { + client := ntlm.V2ClientSession{} + client.SetUserInfo("my_username", "my_invalid_password", "") + + authenticateResponse := authenticate(t, &client) + if authenticateResponse.Authenticated { + t.Errorf("Authenticated with wrong password") + return + } + if authenticateResponse.Username != "" { + t.Errorf("If authentication failed, no username should be returned") + return + } +} + +func TestNtlmInvalidUsername(t *testing.T) { + client := ntlm.V2ClientSession{} + client.SetUserInfo("my_invalid_username", "my_password", "") + + authenticateResponse := authenticate(t, &client) + if authenticateResponse.Authenticated { + t.Errorf("Authenticated with wrong password") + return + } + if authenticateResponse.Username != "" { + t.Errorf("If authentication failed, no username should be returned") + return + } +} + +func authenticate(t *testing.T, client *ntlm.V2ClientSession) (*auth.NtlmResponse) { + session := "X" + database := createTestDatabase() + + server := NewNTLMAuth(database) + + negotiate, err := client.GenerateNegotiateMessage() + if err != nil { + t.Errorf("Could not generate negotiate message: %s", err) + return nil + } + + negotiateRequest := &auth.NtlmRequest{} + negotiateRequest.Session = session + negotiateRequest.NtlmMessage = base64.StdEncoding.EncodeToString(negotiate.Bytes()) + negotiateResponse, err := server.Authenticate(negotiateRequest) + if err != nil { + t.Errorf("Could not generate challenge message: %s", err) + return nil + } + if negotiateResponse.Authenticated { + t.Errorf("User should not be authenticated by after negotiate message") + return nil + } + if negotiateResponse.NtlmMessage == "" { + t.Errorf("Could not generate challenge message") + return nil + } + + decodedChallenge, err := base64.StdEncoding.DecodeString(negotiateResponse.NtlmMessage) + if err != nil { + t.Errorf("Challenge should be base64 encoded: %s", err) + return nil + } + + challenge, err := ntlm.ParseChallengeMessage(decodedChallenge) + if err != nil { + t.Errorf("Invalid challenge message generated: %s", err) + return nil + } + + client.ProcessChallengeMessage(challenge) + authenticate, err := client.GenerateAuthenticateMessage() + if err != nil { + t.Errorf("Could not generate authenticate message: %s", err) + return nil + } + + authenticateRequest := &auth.NtlmRequest{} + authenticateRequest.Session = session + authenticateRequest.NtlmMessage = base64.StdEncoding.EncodeToString(authenticate.Bytes()) + authenticateResponse, err := server.Authenticate(authenticateRequest) + if err != nil { + t.Errorf("Could not parse authenticate message: %s", err) + return authenticateResponse + } + if authenticateResponse.NtlmMessage != "" { + t.Errorf("Authenticate request should not generate a new NTLM message") + return authenticateResponse + } + return authenticateResponse +} + +func TestInvalidBase64 (t *testing.T) { + testInvalidDataBase(t, "X", "X") // not valid base64 +} + +func TestInvalidData (t *testing.T) { + testInvalidDataBase(t, "X", "XXXX") // valid base64 +} + +func TestInvalidDataEmptyMessage (t *testing.T) { + testInvalidDataBase(t, "X", "") +} + +func TestEmptySession (t *testing.T) { + testInvalidDataBase(t, "", "XXXX") +} + +func testInvalidDataBase (t *testing.T, session string, data string) { + database := createTestDatabase() + server := NewNTLMAuth(database) + + request := &auth.NtlmRequest{} + request.Session = session + request.NtlmMessage = data + response, err := server.Authenticate(request) + log.Printf("%s",err) + if err == nil { + t.Errorf("Invalid request should return an error") + } + if response.Authenticated { + t.Errorf("User should not be authenticated using invalid data") + } + if response.NtlmMessage != "" { + t.Errorf("No NTLM message should be generated for invalid data") + } +} diff --git a/cmd/rdpgw/config/configuration.go b/cmd/rdpgw/config/configuration.go index 3202d34..ead4140 100644 --- a/cmd/rdpgw/config/configuration.go +++ b/cmd/rdpgw/config/configuration.go @@ -31,7 +31,6 @@ type Configuration struct { Server ServerConfig `koanf:"server"` OpenId OpenIDConfig `koanf:"openid"` Kerberos KerberosConfig `koanf:"kerberos"` - Users []UserConfig `koanf:"users"` Caps RDGCapsConfig `koanf:"caps"` Security SecurityConfig `koanf:"security"` Client ClientConfig `koanf:"client"` @@ -67,11 +66,6 @@ type OpenIDConfig struct { ClientSecret string `koanf:"clientsecret"` } -type UserConfig struct { - Username string `koanf:"username"` - Password string `koanf:"password"` -} - type RDGCapsConfig struct { SmartCardAuth bool `koanf:"smartcardauth"` TokenAuth bool `koanf:"tokenauth"` @@ -190,7 +184,6 @@ func Load(configFile string) Configuration { k.UnmarshalWithConf("Security", &Conf.Security, koanfTag) k.UnmarshalWithConf("Client", &Conf.Client, koanfTag) k.UnmarshalWithConf("Kerberos", &Conf.Kerberos, koanfTag) - k.UnmarshalWithConf("Users", &Conf.Users, koanfTag) if len(Conf.Security.PAATokenEncryptionKey) != 32 { Conf.Security.PAATokenEncryptionKey, _ = security.GenerateRandomString(32) @@ -228,12 +221,12 @@ func Load(configFile string) Configuration { log.Fatalf("host selection is set to `signed` but `querytokensigningkey` is not set") } - if Conf.Server.LocalEnabled() && Conf.Server.Tls == "disable" { + if Conf.Server.BasicAuthEnabled() && Conf.Server.Tls == "disable" { log.Fatalf("basicauth=local and tls=disable are mutually exclusive") } - if Conf.Server.DatabaseEnabled() && Conf.Server.KerberosEnabled() { - log.Fatalf("database and kerberos authentication are not stackable") + if Conf.Server.NtlmEnabled() && Conf.Server.KerberosEnabled() { + log.Fatalf("ntlm and kerberos authentication are not stackable") } if !Conf.Caps.TokenAuth && Conf.Server.OpenIDEnabled() { @@ -261,12 +254,12 @@ func (s *ServerConfig) KerberosEnabled() bool { return s.matchAuth("kerberos") } -func (s *ServerConfig) LocalEnabled() bool { +func (s *ServerConfig) BasicAuthEnabled() bool { return s.matchAuth("local") || s.matchAuth("basic") } -func (s *ServerConfig) DatabaseEnabled() bool { - return s.matchAuth("database") +func (s *ServerConfig) NtlmEnabled() bool { + return s.matchAuth("ntlm") } func (s *ServerConfig) matchAuth(needle string) bool { diff --git a/cmd/rdpgw/main.go b/cmd/rdpgw/main.go index ec72ec4..528106a 100644 --- a/cmd/rdpgw/main.go +++ b/cmd/rdpgw/main.go @@ -8,7 +8,6 @@ import ( "github.com/bolkedebruin/gokrb5/v8/service" "github.com/bolkedebruin/gokrb5/v8/spnego" "github.com/bolkedebruin/rdpgw/cmd/rdpgw/config" - "github.com/bolkedebruin/rdpgw/cmd/rdpgw/database" "github.com/bolkedebruin/rdpgw/cmd/rdpgw/kdcproxy" "github.com/bolkedebruin/rdpgw/cmd/rdpgw/protocol" "github.com/bolkedebruin/rdpgw/cmd/rdpgw/security" @@ -222,7 +221,7 @@ func main() { r.HandleFunc("/callback", o.HandleCallback) // only enable un-auth endpoint for openid only config - if !conf.Server.KerberosEnabled() && !conf.Server.LocalEnabled() && !conf.Server.DatabaseEnabled() { + if !conf.Server.KerberosEnabled() && !conf.Server.BasicAuthEnabled() && !conf.Server.NtlmEnabled() { rdp.Name("gw").HandlerFunc(gw.HandleGatewayProtocol) } } @@ -231,29 +230,20 @@ func main() { auth := web.NewAuthMux() rdp.MatcherFunc(web.NoAuthz).HandlerFunc(auth.SetAuthenticate) - var db database.Database - - // database - if conf.Server.DatabaseEnabled() { - log.Printf("enabling database authentication") - db = database.NewConfig(conf.Users) - ntlm := web.NewNTLMAuthHandler(db) - rdp.NewRoute().HeadersRegexp("Authorization", "NTLM").HandlerFunc(ntlm.NTLMAuth(gw.HandleGatewayProtocol)) - rdp.NewRoute().HeadersRegexp("Authorization", "Negotiate").HandlerFunc(ntlm.NTLMAuth(gw.HandleGatewayProtocol)) - auth.Register(`NTLM`) + // ntlm + if conf.Server.NtlmEnabled() { + log.Printf("enabling NTLM authentication") + ntlm := web.NTLMAuthHandler{SocketAddress: conf.Server.AuthSocket, Timeout: conf.Server.BasicAuthTimeout} + rdp.NewRoute().HeadersRegexp("Authorization", "NTLM").HandlerFunc(ntlm.NTLMAuth(gw.HandleGatewayProtocol)) + rdp.NewRoute().HeadersRegexp("Authorization", "Negotiate").HandlerFunc(ntlm.NTLMAuth(gw.HandleGatewayProtocol)) + auth.Register(`NTLM`) + auth.Register(`Negotiate`) } // basic auth - if conf.Server.LocalEnabled() || conf.Server.DatabaseEnabled() { - q := web.BasicAuthHandler{} - if conf.Server.LocalEnabled() { - log.Printf("enabling local authentication") - q.SocketAddress = conf.Server.AuthSocket - q.Timeout = conf.Server.BasicAuthTimeout - } - if conf.Server.DatabaseEnabled() { - q.Database = db - } + if conf.Server.BasicAuthEnabled() { + log.Printf("enabling basic authentication") + q := web.BasicAuthHandler{SocketAddress: conf.Server.AuthSocket, Timeout: conf.Server.BasicAuthTimeout} rdp.NewRoute().HeadersRegexp("Authorization", "Basic").HandlerFunc(q.BasicAuth(gw.HandleGatewayProtocol)) auth.Register(`Basic realm="restricted", charset="UTF-8"`) } diff --git a/cmd/rdpgw/web/basic.go b/cmd/rdpgw/web/basic.go index 299738c..b852634 100644 --- a/cmd/rdpgw/web/basic.go +++ b/cmd/rdpgw/web/basic.go @@ -2,7 +2,6 @@ package web import ( "context" - "github.com/bolkedebruin/rdpgw/cmd/rdpgw/database" "github.com/bolkedebruin/rdpgw/cmd/rdpgw/identity" "github.com/bolkedebruin/rdpgw/shared/auth" "google.golang.org/grpc" @@ -20,7 +19,6 @@ const ( type BasicAuthHandler struct { SocketAddress string Timeout int - Database database.Database } func (h *BasicAuthHandler) BasicAuth(next http.HandlerFunc) http.HandlerFunc { @@ -51,11 +49,6 @@ func (h *BasicAuthHandler) BasicAuth(next http.HandlerFunc) http.HandlerFunc { } func (h *BasicAuthHandler) authenticate(w http.ResponseWriter, r *http.Request, username string, password string) (authenticated bool) { - return h.authenticateDatabase(username, password) || - h.authenticateSocket(w, r, username, password) -} - -func (h *BasicAuthHandler) authenticateSocket(w http.ResponseWriter, r *http.Request, username string, password string) (authenticated bool) { if h.SocketAddress == "" { return false } @@ -87,12 +80,3 @@ func (h *BasicAuthHandler) authenticateSocket(w http.ResponseWriter, r *http.Req return res.Authenticated } - -func (h *BasicAuthHandler) authenticateDatabase(username string, password string) (authenticated bool) { - if h.Database == nil { - return false - } - - expectedPassword := h.Database.GetPassword (username) - return expectedPassword != "" && password == expectedPassword -} \ No newline at end of file diff --git a/cmd/rdpgw/web/ntlm.go b/cmd/rdpgw/web/ntlm.go index dfcbe47..b4fc89d 100644 --- a/cmd/rdpgw/web/ntlm.go +++ b/cmd/rdpgw/web/ntlm.go @@ -1,23 +1,18 @@ package web import ( - "encoding/base64" + "context" "errors" - "fmt" - "github.com/bolkedebruin/rdpgw/cmd/rdpgw/database" "github.com/bolkedebruin/rdpgw/cmd/rdpgw/identity" - "github.com/patrickmn/go-cache" - "github.com/m7913d/go-ntlm/ntlm" + "github.com/bolkedebruin/rdpgw/shared/auth" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" "log" + "net" "net/http" "time" ) -const ( - cacheExpiration = time.Minute - cleanupInterval = time.Minute * 5 -) - type ntlmAuthMode uint32 const ( authNone ntlmAuthMode = iota @@ -26,197 +21,100 @@ const ( ) type NTLMAuthHandler struct { - contextCache *cache.Cache - - // Information about the server, returned to the client during authentication - ServerName string // e.g. EXAMPLE1 - DomainName string // e.g. EXAMPLE - DnsServerName string // e.g. example1.example.com - DnsDomainName string // e.g. example.com - DnsTreeName string // e.g. example.com - - Database database.Database -} - -func NewNTLMAuthHandler (database database.Database) (*NTLMAuthHandler) { - return &NTLMAuthHandler{ - contextCache: cache.New(cacheExpiration, cleanupInterval), - Database: database, - } + SocketAddress string + Timeout int } func (h *NTLMAuthHandler) NTLMAuth(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - //log.Printf("NTLM request") - - authPayload, authMode, err := h.getAuthPayload(r) + authPayload, authMode, err := h.getAuthPayload(r) if err != nil { log.Printf("Failed parsing auth header: %s", err) - w.Header().Add("WWW-Authenticate", `NTLM,Basic realm="restricted", charset="UTF-8"`) - http.Error(w, "Unauthorized", http.StatusUnauthorized) + h.requestAuthenticate(w) return - } - - c := h.getContext(r) - if c.Auth(w, authPayload, authMode) { - username := c.GetUsername() - log.Printf("NTLM: User %s authenticated", username) - id := identity.FromRequestCtx(r) - id.SetUserName(username) - id.SetAuthenticated(true) - id.SetAuthTime(time.Now()) - next.ServeHTTP(w, identity.AddToRequestCtx(id, r)) - } - } -} - -func (h *NTLMAuthHandler) getAuthPayload (r *http.Request) (payload string, authMode ntlmAuthMode, err error) { - authorisationEncoded := r.Header.Get("Authorization") - if authorisationEncoded[0:5] == "NTLM " { - return authorisationEncoded[5:], authNTLM, nil - } - if authorisationEncoded[0:10] == "Negotiate " { - return authorisationEncoded[10:], authNegotiate, nil - } - return "", authNone, errors.New(fmt.Sprintf("Invalid NTLM Authorisation header: %s", authorisationEncoded)) -} - -func (h *NTLMAuthHandler) getContext (r *http.Request) (*ntlmContext) { - if c_, found := h.contextCache.Get(r.RemoteAddr); found { - if c, ok := c_.(*ntlmContext); ok { - return c - } - } - c := new(ntlmContext) - c.h = h - h.contextCache.Set(r.RemoteAddr, c, cache.DefaultExpiration) - return c -} - -type ntlmContext struct { - session ntlm.ServerSession - h *NTLMAuthHandler -} - -func (c *ntlmContext) GetUsername () (username string) { - username, _, _ = c.session.GetUserInfo() - return username -} + } -func (c *ntlmContext) Auth(w http.ResponseWriter, authorisationEncoded string, authMode ntlmAuthMode) (succeeded bool) { - authorisation, err := base64.StdEncoding.DecodeString(authorisationEncoded) - if err != nil { - log.Printf("Failed to decode NTLM Authorisation header: %s due to: %s", authorisationEncoded, err) - http.Error(w, "Server error", http.StatusInternalServerError) - return false - } - - nm, err := ntlm.ParseNegotiateMessage(authorisation) - if err == nil { - c.negotiate(w, nm, authMode) - return false - } - if (nm != nil && nm.MessageType == 1) { - log.Printf("Failed to parse NTLM Authorisation header: %s due to %s", authorisationEncoded, err) - http.Error(w, "Server error", http.StatusInternalServerError) - return false - } else if c.session == nil { - log.Printf("New NTLM auth sequence should start with negotioate request: %s", authorisationEncoded) - http.Error(w, "Server error", http.StatusInternalServerError) - return false - } + authenticated, username := h.authenticate(w, r, authPayload, authMode) - am, err := ntlm.ParseAuthenticateMessage(authorisation, 2) - if err == nil { - return c.authenticate(w, am) + if authenticated { + log.Printf("NTLM: User %s authenticated", username) + id := identity.FromRequestCtx(r) + id.SetUserName(username) + id.SetAuthenticated(true) + id.SetAuthTime(time.Now()) + next.ServeHTTP(w, identity.AddToRequestCtx(id, r)) + } } - - log.Printf("Failed to parse NTLM Authorisation header: %s due to %s", authorisationEncoded, err) - http.Error(w, "Server error", http.StatusInternalServerError) - return false } -func (c *ntlmContext) getAuthPrefix (authMode ntlmAuthMode) (prefix string) { - if authMode == authNTLM { - return "NTLM " - } - if authMode == authNegotiate { - return "Negotiate " - } - return "" +func (h *NTLMAuthHandler) getAuthPayload (r *http.Request) (payload string, authMode ntlmAuthMode, err error) { + authorisationEncoded := r.Header.Get("Authorization") + if authorisationEncoded[0:5] == "NTLM " { + return authorisationEncoded[5:], authNTLM, nil + } + if authorisationEncoded[0:10] == "Negotiate " { + return authorisationEncoded[10:], authNegotiate, nil + } + return "", authNone, errors.New("Invalid NTLM Authorisation header") } -func (c *ntlmContext) requestAuthenticate (w http.ResponseWriter) { - w.Header().Add("WWW-Authenticate", `NTLM,Basic realm="restricted", charset="UTF-8"`) - http.Error(w, "Unauthorized", http.StatusUnauthorized) +func (h *NTLMAuthHandler) requestAuthenticate (w http.ResponseWriter) { + w.Header().Add("WWW-Authenticate", `NTLM`) + w.Header().Add("WWW-Authenticate", `Negotiate`) + http.Error(w, "Unauthorized", http.StatusUnauthorized) } -func (c *ntlmContext) negotiate(w http.ResponseWriter, nm *ntlm.NegotiateMessage, authMode ntlmAuthMode) { - //log.Printf("NTLM negotiate request: %v", nm) - - session, err := ntlm.CreateServerSession(ntlm.Version2, ntlm.ConnectionOrientedMode) - - if err != nil { - c.session = nil; - log.Printf("Failed to create NTLM server session: %s", err) - http.Error(w, "Server error", http.StatusInternalServerError) - return - } - - c.session = session - c.session.SetDomainName(c.h.DomainName) - c.session.SetComputerName(c.h.ServerName) - c.session.SetDnsDomainName(c.h.DnsDomainName) - c.session.SetDnsComputerName(c.h.DnsServerName) - c.session.SetDnsTreeName(c.h.DnsTreeName) - - err = c.session.ProcessNegotiateMessage(nm) - if err != nil { - log.Printf("Failed to process NTLM negotiate message: %s", err) - http.Error(w, "Server error", http.StatusInternalServerError) - return - } - - cm, err := c.session.GenerateChallengeMessage() - if err != nil { - log.Printf("Failed to generate NTLM challenge message: %s", err) - http.Error(w, "Server error", http.StatusInternalServerError) - return - } - - log.Printf("Sending NTLM challenge request") - //log.Printf("Sending NTLM challenge request: %v", cm) - - cmBytes := cm.Bytes() - w.Header().Add("WWW-Authenticate", c.getAuthPrefix(authMode)+base64.StdEncoding.EncodeToString(cmBytes)) - http.Error(w, "Unauthorized", http.StatusUnauthorized) +func (h *NTLMAuthHandler) getAuthPrefix (authMode ntlmAuthMode) (prefix string) { + if authMode == authNTLM { + return "NTLM " + } + if authMode == authNegotiate { + return "Negotiate " + } + return "" } -func (c *ntlmContext) authenticate(w http.ResponseWriter, am *ntlm.AuthenticateMessage) (succeeded bool) { - //log.Printf("NTLM Authenticate request: %v", am) - - if c.session == nil { - log.Printf("NTLM Authenticate requires active session: first call negotioate") - http.Error(w, "Server error", http.StatusInternalServerError) - return false - } - - username := am.UserName.String() - password := c.h.Database.GetPassword (username) - if password == "" { - log.Printf("NTLM: unknown username specified: %s", username) - c.requestAuthenticate(w) - return false - } - - c.session.SetUserInfo(username,password,"") - - err := c.session.ProcessAuthenticateMessage(am) - if err != nil { - log.Printf("Failed to process NTLM authenticate message: %s", err) - c.requestAuthenticate(w) - return false - } - - return true +func (h *NTLMAuthHandler) authenticate(w http.ResponseWriter, r *http.Request, authorisationEncoded string, authMode ntlmAuthMode) (authenticated bool, username string) { + if h.SocketAddress == "" { + return false, "" + } + + ctx := r.Context() + + conn, err := grpc.Dial(h.SocketAddress, grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { + return net.Dial(protocolGrpc, addr) + })) + if err != nil { + log.Printf("Cannot reach authentication provider: %s", err) + http.Error(w, "Server error", http.StatusInternalServerError) + return false, "" + } + defer conn.Close() + + c := auth.NewAuthenticateClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(h.Timeout)) + defer cancel() + + req := &auth.NtlmRequest{Session: r.RemoteAddr, NtlmMessage: authorisationEncoded} + res, err := c.NTLM(ctx, req) + if err != nil { + log.Printf("Error talking to authentication provider: %s", err) + http.Error(w, "Server error", http.StatusInternalServerError) + return false, "" + } + + if res.NtlmMessage != "" { + log.Printf("Sending NTLM challenge") + w.Header().Add("WWW-Authenticate", h.getAuthPrefix(authMode)+res.NtlmMessage) + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return false, "" + } + + if !res.Authenticated { + h.requestAuthenticate(w) + return false, "" + } + + return res.Authenticated, res.Username } diff --git a/proto/auth.proto b/proto/auth.proto index de07903..af603e3 100644 --- a/proto/auth.proto +++ b/proto/auth.proto @@ -14,6 +14,18 @@ message AuthResponse { string error = 2; } +message NtlmRequest { + string session = 1; + string ntlmMessage = 2; +} + +message NtlmResponse { + bool authenticated = 1; + string username = 2; + string ntlmMessage = 3; +} + service Authenticate { rpc Authenticate (UserPass) returns (AuthResponse) {} -} \ No newline at end of file + rpc NTLM (NtlmRequest) returns (NtlmResponse) {} +} diff --git a/shared/auth/auth.pb.go b/shared/auth/auth.pb.go index 5bac29c..5905449 100644 --- a/shared/auth/auth.pb.go +++ b/shared/auth/auth.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.5 +// protoc-gen-go v1.25.0-devel +// protoc v3.14.0 // source: auth.proto package auth @@ -130,6 +130,132 @@ func (x *AuthResponse) GetError() string { return "" } +type NtlmRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Session string `protobuf:"bytes,1,opt,name=session,proto3" json:"session,omitempty"` + NtlmMessage string `protobuf:"bytes,2,opt,name=ntlmMessage,proto3" json:"ntlmMessage,omitempty"` +} + +func (x *NtlmRequest) Reset() { + *x = NtlmRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_auth_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NtlmRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NtlmRequest) ProtoMessage() {} + +func (x *NtlmRequest) ProtoReflect() protoreflect.Message { + mi := &file_auth_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NtlmRequest.ProtoReflect.Descriptor instead. +func (*NtlmRequest) Descriptor() ([]byte, []int) { + return file_auth_proto_rawDescGZIP(), []int{2} +} + +func (x *NtlmRequest) GetSession() string { + if x != nil { + return x.Session + } + return "" +} + +func (x *NtlmRequest) GetNtlmMessage() string { + if x != nil { + return x.NtlmMessage + } + return "" +} + +type NtlmResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Authenticated bool `protobuf:"varint,1,opt,name=authenticated,proto3" json:"authenticated,omitempty"` + Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` + NtlmMessage string `protobuf:"bytes,3,opt,name=ntlmMessage,proto3" json:"ntlmMessage,omitempty"` + Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *NtlmResponse) Reset() { + *x = NtlmResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_auth_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NtlmResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NtlmResponse) ProtoMessage() {} + +func (x *NtlmResponse) ProtoReflect() protoreflect.Message { + mi := &file_auth_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NtlmResponse.ProtoReflect.Descriptor instead. +func (*NtlmResponse) Descriptor() ([]byte, []int) { + return file_auth_proto_rawDescGZIP(), []int{3} +} + +func (x *NtlmResponse) GetAuthenticated() bool { + if x != nil { + return x.Authenticated + } + return false +} + +func (x *NtlmResponse) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *NtlmResponse) GetNtlmMessage() string { + if x != nil { + return x.NtlmMessage + } + return "" +} + +func (x *NtlmResponse) GetError() string { + if x != nil { + return x.Error + } + return "" +} + var File_auth_proto protoreflect.FileDescriptor var file_auth_proto_rawDesc = []byte{ @@ -143,12 +269,29 @@ var file_auth_proto_rawDesc = []byte{ 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x32, 0x44, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x12, 0x34, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x12, 0x0e, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x61, - 0x73, 0x73, 0x1a, 0x12, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x61, 0x75, - 0x74, 0x68, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x72, 0x22, 0x49, 0x0a, 0x0b, 0x4e, 0x74, 0x6c, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x6e, + 0x74, 0x6c, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x6e, 0x74, 0x6c, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x88, 0x01, + 0x0a, 0x0c, 0x4e, 0x74, 0x6c, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, + 0x0a, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x20, 0x0a, 0x0b, 0x6e, 0x74, 0x6c, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x74, 0x6c, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x32, 0x75, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, + 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x34, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, + 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x50, 0x61, 0x73, 0x73, 0x1a, 0x12, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, + 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2f, + 0x0a, 0x04, 0x4e, 0x54, 0x4c, 0x4d, 0x12, 0x11, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x4e, 0x74, + 0x6c, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x75, 0x74, 0x68, + 0x2e, 0x4e, 0x74, 0x6c, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, + 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -163,16 +306,20 @@ func file_auth_proto_rawDescGZIP() []byte { return file_auth_proto_rawDescData } -var file_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_auth_proto_goTypes = []interface{}{ (*UserPass)(nil), // 0: auth.UserPass (*AuthResponse)(nil), // 1: auth.AuthResponse + (*NtlmRequest)(nil), // 2: auth.NtlmRequest + (*NtlmResponse)(nil), // 3: auth.NtlmResponse } var file_auth_proto_depIdxs = []int32{ 0, // 0: auth.Authenticate.Authenticate:input_type -> auth.UserPass - 1, // 1: auth.Authenticate.Authenticate:output_type -> auth.AuthResponse - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type + 2, // 1: auth.Authenticate.NTLM:input_type -> auth.NtlmRequest + 1, // 2: auth.Authenticate.Authenticate:output_type -> auth.AuthResponse + 3, // 3: auth.Authenticate.NTLM:output_type -> auth.NtlmResponse + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name @@ -208,6 +355,30 @@ func file_auth_proto_init() { return nil } } + file_auth_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NtlmRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_auth_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NtlmResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -215,7 +386,7 @@ func file_auth_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_auth_proto_rawDesc, NumEnums: 0, - NumMessages: 2, + NumMessages: 4, NumExtensions: 0, NumServices: 1, }, diff --git a/shared/auth/auth_grpc.pb.go b/shared/auth/auth_grpc.pb.go index f279286..6efd534 100644 --- a/shared/auth/auth_grpc.pb.go +++ b/shared/auth/auth_grpc.pb.go @@ -1,8 +1,4 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.21.5 -// source: auth.proto package auth @@ -23,6 +19,7 @@ const _ = grpc.SupportPackageIsVersion7 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type AuthenticateClient interface { Authenticate(ctx context.Context, in *UserPass, opts ...grpc.CallOption) (*AuthResponse, error) + NTLM(ctx context.Context, in *NtlmRequest, opts ...grpc.CallOption) (*NtlmResponse, error) } type authenticateClient struct { @@ -42,20 +39,35 @@ func (c *authenticateClient) Authenticate(ctx context.Context, in *UserPass, opt return out, nil } +func (c *authenticateClient) NTLM(ctx context.Context, in *NtlmRequest, opts ...grpc.CallOption) (*NtlmResponse, error) { + out := new(NtlmResponse) + err := c.cc.Invoke(ctx, "/auth.Authenticate/NTLM", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // AuthenticateServer is the server API for Authenticate service. -// All implementations should embed UnimplementedAuthenticateServer +// All implementations must embed UnimplementedAuthenticateServer // for forward compatibility type AuthenticateServer interface { Authenticate(context.Context, *UserPass) (*AuthResponse, error) + NTLM(context.Context, *NtlmRequest) (*NtlmResponse, error) + mustEmbedUnimplementedAuthenticateServer() } -// UnimplementedAuthenticateServer should be embedded to have forward compatible implementations. +// UnimplementedAuthenticateServer must be embedded to have forward compatible implementations. type UnimplementedAuthenticateServer struct { } func (UnimplementedAuthenticateServer) Authenticate(context.Context, *UserPass) (*AuthResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Authenticate not implemented") } +func (UnimplementedAuthenticateServer) NTLM(context.Context, *NtlmRequest) (*NtlmResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method NTLM not implemented") +} +func (UnimplementedAuthenticateServer) mustEmbedUnimplementedAuthenticateServer() {} // UnsafeAuthenticateServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to AuthenticateServer will @@ -86,6 +98,24 @@ func _Authenticate_Authenticate_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _Authenticate_NTLM_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(NtlmRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthenticateServer).NTLM(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/auth.Authenticate/NTLM", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthenticateServer).NTLM(ctx, req.(*NtlmRequest)) + } + return interceptor(ctx, in, info, handler) +} + // Authenticate_ServiceDesc is the grpc.ServiceDesc for Authenticate service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -97,6 +127,10 @@ var Authenticate_ServiceDesc = grpc.ServiceDesc{ MethodName: "Authenticate", Handler: _Authenticate_Authenticate_Handler, }, + { + MethodName: "NTLM", + Handler: _Authenticate_NTLM_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "auth.proto",