mirror of
https://github.com/bolkedebruin/rdpgw.git
synced 2025-08-12 11:59:18 +02:00
Support for NTLM authentication added (#109)
* Support for NTLM authentication added To support NTLM authentication, a database is added as an authentication source. Currently, only the configuration file is supported as a database. Database authentication supports Basic and NTLM authentication protcols. ServerConfig.BasicAuthEnabled renamed to LocalEnabled as Basic auth can be used with NTLM or Local.
This commit is contained in:
parent
7472c7b2c1
commit
372dc43ef2
16 changed files with 947 additions and 64 deletions
66
README.md
66
README.md
|
@ -28,7 +28,7 @@ to connect.
|
|||
|
||||
The gateway has several security phases. In the authentication phase the client's credentials are
|
||||
verified. Depending the authentication mechanism used, the client's credentials are verified against
|
||||
an OpenID Connect provider, Kerberos or a local PAM service.
|
||||
an OpenID Connect provider, Kerberos, a local PAM service or a local database.
|
||||
|
||||
If OpenID Connect is used the user will
|
||||
need to connect to a webpage provided by the gateway to authenticate, which in turn will redirect
|
||||
|
@ -45,6 +45,9 @@ 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 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
|
||||
using OpenID Connect. Due to proxies and NAT this is not always possible and thus can be disabled. However, this
|
||||
|
@ -58,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 and PAM.
|
||||
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)
|
||||
|
@ -72,7 +75,7 @@ if you want.
|
|||
|
||||
### Mixing authentication mechanisms
|
||||
|
||||
It is technically possible to mix authentication mechanisms. Currently, you can mix local and Kerberos. 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
|
||||
|
@ -137,7 +140,7 @@ Caps:
|
|||
The client can then connect directly to the gateway without the need for a RDP file.
|
||||
|
||||
|
||||
### PAM / Local / Basic Auth
|
||||
### PAM / Local (Basic Auth)
|
||||

|
||||
|
||||
The gateway can also support authentication against PAM. Sometimes this is referred to as local or passwd authentication,
|
||||
|
@ -146,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 or Kerberos.
|
||||
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
|
||||
|
@ -180,6 +183,38 @@ 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.
|
||||
|
||||
### NTLM
|
||||
|
||||
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.
|
||||
|
||||
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 NTLM authentication make sure to set the following variables in the configuration file.
|
||||
|
||||
Configuration file for `rdpgw`:
|
||||
```yaml
|
||||
Server:
|
||||
Authentication:
|
||||
- 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.
|
||||
|
||||
## TLS
|
||||
|
||||
The gateway requires a valid TLS certificate. This means a certificate that is signed by a valid CA that is in the store
|
||||
|
@ -209,16 +244,17 @@ TLS termination.
|
|||
```yaml
|
||||
# web server configuration.
|
||||
Server:
|
||||
# can be set to openid, kerberos and local. 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 and kerberos authentication
|
||||
# can be stacked, so that the clients selects what it wants.
|
||||
# used a keytab and krb5conf need to be supplied. local can be stacked with
|
||||
# kerberos or ntlm authentication, so that the clients selects what it wants.
|
||||
Authentication:
|
||||
# - kerberos
|
||||
# - local
|
||||
- openid
|
||||
# - 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
|
||||
|
@ -369,12 +405,15 @@ In this way you can integrate, for example, it with [pam-jwt](https://github.com
|
|||
## Client Caveats
|
||||
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
|
||||
or Kerberos.
|
||||
not support basic authentication. This means you need to use either OpenID Connect,
|
||||
Kerberos or ntlm authentication.
|
||||
|
||||
In addition to that, ``mstsc``, when configuring a gateway directly in the client requires
|
||||
you to "save the credentials" for the gateway otherwise the client will not connect at all
|
||||
(it won't send any packages to the gateway) and it will keep on asking for new credentials.
|
||||
you to either:
|
||||
* "save the credentials" for the gateway
|
||||
* or specify a (random) domain name in the username field (e.g. ``.\username``) when prompted for the gateway credentials,
|
||||
|
||||
otherwise the client will not connect at all (it won't send any packages to the gateway) and it will keep on asking for new credentials.
|
||||
|
||||
Finally, ``mstsc`` requires a valid certificate on the gateway.
|
||||
|
||||
|
@ -393,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)
|
||||
|
|
|
@ -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,11 +88,34 @@ 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() {
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
42
cmd/auth/config/configuration.go
Normal file
42
cmd/auth/config/configuration.go
Normal file
|
@ -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
|
||||
|
||||
}
|
25
cmd/auth/database/config.go
Executable file
25
cmd/auth/database/config.go
Executable file
|
@ -0,0 +1,25 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"github.com/bolkedebruin/rdpgw/cmd/auth/config"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
users map[string]config.UserConfig
|
||||
}
|
||||
|
||||
func NewConfig(users []config.UserConfig) *Config {
|
||||
usersMap := map[string]config.UserConfig{}
|
||||
|
||||
for _, user := range users {
|
||||
usersMap[user.Username] = user
|
||||
}
|
||||
|
||||
return &Config{
|
||||
users: usersMap,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) GetPassword (username string) string {
|
||||
return c.users[username].Password
|
||||
}
|
43
cmd/auth/database/config_test.go
Normal file
43
cmd/auth/database/config_test.go
Normal file
|
@ -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")
|
||||
}
|
||||
}
|
5
cmd/auth/database/database.go
Executable file
5
cmd/auth/database/database.go
Executable file
|
@ -0,0 +1,5 @@
|
|||
package database
|
||||
|
||||
type Database interface {
|
||||
GetPassword (username string) string
|
||||
}
|
160
cmd/auth/ntlm/ntlm.go
Normal file
160
cmd/auth/ntlm/ntlm.go
Normal file
|
@ -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
|
||||
}
|
168
cmd/auth/ntlm/ntlm_test.go
Normal file
168
cmd/auth/ntlm/ntlm_test.go
Normal file
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -225,6 +225,10 @@ func Load(configFile string) Configuration {
|
|||
log.Fatalf("basicauth=local and tls=disable are mutually exclusive")
|
||||
}
|
||||
|
||||
if Conf.Server.NtlmEnabled() && Conf.Server.KerberosEnabled() {
|
||||
log.Fatalf("ntlm and kerberos authentication are not stackable")
|
||||
}
|
||||
|
||||
if !Conf.Caps.TokenAuth && Conf.Server.OpenIDEnabled() {
|
||||
log.Fatalf("openid is configured but tokenauth disabled")
|
||||
}
|
||||
|
@ -254,6 +258,10 @@ func (s *ServerConfig) BasicAuthEnabled() bool {
|
|||
return s.matchAuth("local") || s.matchAuth("basic")
|
||||
}
|
||||
|
||||
func (s *ServerConfig) NtlmEnabled() bool {
|
||||
return s.matchAuth("ntlm")
|
||||
}
|
||||
|
||||
func (s *ServerConfig) matchAuth(needle string) bool {
|
||||
for _, q := range s.Authentication {
|
||||
if q == needle {
|
||||
|
|
|
@ -221,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.BasicAuthEnabled() {
|
||||
if !conf.Server.KerberosEnabled() && !conf.Server.BasicAuthEnabled() && !conf.Server.NtlmEnabled() {
|
||||
rdp.Name("gw").HandlerFunc(gw.HandleGatewayProtocol)
|
||||
}
|
||||
}
|
||||
|
@ -230,6 +230,16 @@ func main() {
|
|||
auth := web.NewAuthMux()
|
||||
rdp.MatcherFunc(web.NoAuthz).HandlerFunc(auth.SetAuthenticate)
|
||||
|
||||
// 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.BasicAuthEnabled() {
|
||||
log.Printf("enabling basic authentication")
|
||||
|
|
|
@ -25,6 +25,34 @@ func (h *BasicAuthHandler) BasicAuth(next http.HandlerFunc) http.HandlerFunc {
|
|||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
username, password, ok := r.BasicAuth()
|
||||
if ok {
|
||||
authenticated := h.authenticate(w, r, username, password)
|
||||
|
||||
if !authenticated {
|
||||
log.Printf("User %s is not authenticated for this service", username)
|
||||
} else {
|
||||
log.Printf("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))
|
||||
return
|
||||
}
|
||||
}
|
||||
// If the Authentication header is not present, is invalid, or the
|
||||
// username or password is wrong, then set a WWW-Authenticate
|
||||
// header to inform the client that we expect them to use basic
|
||||
// authentication and send a 401 Unauthorized response.
|
||||
w.Header().Add("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BasicAuthHandler) authenticate(w http.ResponseWriter, r *http.Request, username string, password string) (authenticated bool) {
|
||||
if h.SocketAddress == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
conn, err := grpc.Dial(h.SocketAddress, grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
|
@ -34,7 +62,7 @@ func (h *BasicAuthHandler) BasicAuth(next http.HandlerFunc) http.HandlerFunc {
|
|||
if err != nil {
|
||||
log.Printf("Cannot reach authentication provider: %s", err)
|
||||
http.Error(w, "Server error", http.StatusInternalServerError)
|
||||
return
|
||||
return false
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
|
@ -47,27 +75,8 @@ func (h *BasicAuthHandler) BasicAuth(next http.HandlerFunc) http.HandlerFunc {
|
|||
if err != nil {
|
||||
log.Printf("Error talking to authentication provider: %s", err)
|
||||
http.Error(w, "Server error", http.StatusInternalServerError)
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
if !res.Authenticated {
|
||||
log.Printf("User %s is not authenticated for this service", username)
|
||||
} else {
|
||||
log.Printf("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))
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
// If the Authentication header is not present, is invalid, or the
|
||||
// username or password is wrong, then set a WWW-Authenticate
|
||||
// header to inform the client that we expect them to use basic
|
||||
// authentication and send a 401 Unauthorized response.
|
||||
w.Header().Add("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
}
|
||||
return res.Authenticated
|
||||
}
|
||||
|
|
120
cmd/rdpgw/web/ntlm.go
Normal file
120
cmd/rdpgw/web/ntlm.go
Normal file
|
@ -0,0 +1,120 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/identity"
|
||||
"github.com/bolkedebruin/rdpgw/shared/auth"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ntlmAuthMode uint32
|
||||
const (
|
||||
authNone ntlmAuthMode = iota
|
||||
authNTLM
|
||||
authNegotiate
|
||||
)
|
||||
|
||||
type NTLMAuthHandler struct {
|
||||
SocketAddress string
|
||||
Timeout int
|
||||
}
|
||||
|
||||
func (h *NTLMAuthHandler) NTLMAuth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
authPayload, authMode, err := h.getAuthPayload(r)
|
||||
if err != nil {
|
||||
log.Printf("Failed parsing auth header: %s", err)
|
||||
h.requestAuthenticate(w)
|
||||
return
|
||||
}
|
||||
|
||||
authenticated, username := h.authenticate(w, r, authPayload, authMode)
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 (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 (h *NTLMAuthHandler) getAuthPrefix (authMode ntlmAuthMode) (prefix string) {
|
||||
if authMode == authNTLM {
|
||||
return "NTLM "
|
||||
}
|
||||
if authMode == authNegotiate {
|
||||
return "Negotiate "
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
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
|
||||
}
|
1
go.mod
1
go.mod
|
@ -19,6 +19,7 @@ require (
|
|||
github.com/knadh/koanf/providers/env v0.1.0
|
||||
github.com/knadh/koanf/providers/file v0.1.0
|
||||
github.com/knadh/koanf/v2 v2.1.0
|
||||
github.com/m7913d/go-ntlm v0.0.1
|
||||
github.com/msteinert/pam/v2 v2.0.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
|
|
|
@ -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) {}
|
||||
rpc NTLM (NtlmRequest) returns (NtlmResponse) {}
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue