You are not logged in.
I've committed a corresponding patch.
See http://synopse.info/fossil/info/2fa720de1c
In this version, I got rid of the global SecKerberosSPN variable and just added a aSecKerberosSPN parameter to function ClientSSPIAuth().
Sounds more convenient, and re-entrant (several client connections can co-exist with this scheme).
On the end-user side, setting UserName='' and Password='mymormotservice/myserver.mydomain.tld' will enable Kerberos.
Thanks a lot for the input!
Documentation has also been updated.
Here is an extract:
By default, the hash of the user password is stored safely on the server side. This may be an issue for corporate applications, since a new user name / password pair is to be defined by each client, which may be annoying.
Since revision 1.18 of the framework, mORMot is able to use Windows Authentication to identify any user. That is, the user does not need to enter any name nor password, but her/his Windows credentials, as entered at Windows session startup, will be used.
If the SSPIAUTH conditional is defined (which is the default), any call to TSQLRestClientURI.SetUser() method with a void aUserName parameter will try to use current logged name and password to perform a secure Client-Server authentication. It will in fact call the class function TSQLRestServerAuthenticationSSPI.ClientSetUser() method.
In this case, the aPassword parameter will identify if either NTLM or Kerberos authentication scheme is to be used: it may contain the SPN domain name to enabled Kerberos - see next section. This will be transparent to the framework, and a regular session will be created on success.
Only prerequisite is that the TSQLAuthUser table shall contain a corresponding entry, with its LogonName column equals to 'DomainNameUserName' value. This data row won't be created automatically, since it is up to the application to allow or disallow access from an authenticated user: you can be member of the domain, but not eligible to the application.
Kerberos is the preferred authentication protocol for Windows Server 2003 and subsequent Active Directory domains.
Kerberos authentication offers the following advantages over NTLM authentication:
- Mutual authentication.
When a client uses the Kerberos protocol for authentication with a particular service on a particular server, Kerberos provides the client with an assurance that the service is not being impersonated by malicious code on the network.
- Simplified trust management.
Networks with multiple domains no longer require a complex set of explicit, point-to-point trust relationships.
- Enhanced security.
The old NTLM protocol suffers from several weaknesses, which have been fixed by Kerberos.
- Performance.
Offers improved performance, mostly for server applications.
Requirements for Kerberos authentication are the following:
- Client and Server must join a domain, and the trusted third party must exist; if client and server are in different domain, these two domains must be configured as two-way trust.
- SPN must have been registered properly. Service Principal Name (SPNs) are unique identifiers for services running on servers. Each service that will use Kerberos authentication needs to have an SPN set for it so that clients can identify the service on the network. It is registered in Active Directory under either a computer account or a user account. See below for corresponding instructions.
Typical use case of either Kerberos or NTLM are defined by the aPassword parameter:
- Kerberos is used for a remote connection over a network and if aPassword is set to the expected SPN domain;
- NTLM is used over network connection if aPassoword is empty;
- NTLM is used when making local connection.
Note that Kerberos is used only when making remote connection over a network; NTLM is used when making local connection..
To enable Kerberos authentication in mORMot, you need to register SPN for your service.
The format of an SPN is ServiceClass/Host:Port/ServiceName. Typically, SPN for your service, developed with mORMot, looks like mymormotservice/myserver.mydomain.tld or http/myserver.mydomain.tld.
To list SPNs of a computer named MYSERVER, at the command prompt, type:
setspn -l myserver
Typically, you can see the following output:
Registered ServicePrincipalNames for CN=MYSERVER,OU=Computers,DC=domain,DC=tld:
HOST/MYSERVER.domain.tld
HOST/MYSERVER
If your service runs under SYSTEM or Network Service machine accounts, you can test Kerberos authentication by setting the aPassword parameter to value 'HOST/MYSERVER.domain.tld' in the client code and run the application.
To register SPN for your service, at the command prompt, type:
- If your service run under SYSTEM or Network Service machine accounts:
setspn -a mymormotservice/myserver.mydomain.tld myserver
- If your service run under another domain account:
setspn -a mymormotservice/myserver.mydomain.tld myserviceaccount
Membership in Domain Admins group, or equivalent, is the minimum required to complete this procedure.
See http://technet.microsoft.com/en-us/libr … 31241.aspx for more details.
After registration, you can connect to the server as such:
MyClient.SetUser('','mymormotservice/myserver.mydomain.tld'); // will use Kerberos
For good old NTLM, you can run:
MyClient.SetUser('',''); // will use NTLM
Or directly call the TSQLRestServerAuthenticationSSPI.ClientSetUser() method.
The authentication mode used will appear in the log file, if you define WITHLOG conditional when building the service application and if sllUserAuth is in TSQLLog.Family.Level set.
Messages will be as follows:
NTLM Authentication success for domain\myuser
Kerberos Authentication success for domain\myuser
The framework authorization will then be processed as usual, for all features like RESTful ORM process and remote services.
Offline
Small proposal:
we need to initialize TSQLRestServer.fSessionCounter in some not 0 value. In current realization if we use non-signed auth scheme ( TSQLRestServerAuthenticationNone for example) we have such collision:
user A got session ID = 1
user B got session ID = 2
I restart server
user B call auth - got session ID = 1
user A call RetrieveSession with his old session ID = 1 and got credential of user B
my proposial is:
constructor TSQLRestServer.Create;
......
with SystemTime do // set start session counter = miliseconds from midnight
fSessionCounter := (wHour*60*60 + wMinute * 60 + wSecond)*1000 + wMilliSeconds;
not good also but partially help.
Offline
Good idea.
I've committed something like this.
See http://synopse.info/fossil/info/37406b3408
But perhaps a global seed per server execution should make sense.
Offline
Another proposal:
I fight with Negotiate auth from browser. I found all browser try to use Kerberos scheme even if in local network. So if my server not a windows service I can't authorize clients using windows credential. Solution is to force NTLM auth ( pass WWW-Authenticate: NTLM in header).
As for me we need to redefine this section:
const
/// identification name used for SSPI authentication
SECPKGNAMEINTERNAL = 'Negotiate';
/// HTTP header to be set for SSPI authentication
SECPKGNAMEHTTPWWWAUTHENTICATE = 'WWW-Authenticate: '+SECPKGNAMEINTERNAL;
/// HTTP header pattern received for SSPI authentication
SECPKGNAMEHTTPAUTHORIZATION = 'AUTHORIZATION: NEGOTIATE ';
in this way
/// force NTLM authorization instead of Negotiate
/// used it we start our server application not as a Windows service but as a application
procedure ForceNTLM(IsNTLM: boolean);
var
/// identification name used for SSPI authentication 'Negotiate' or 'NTLM';
SECPKGNAMEINTERNAL: RawUTF8;
/// HTTP header to be set for SSPI authentication 'WWW-Authenticate: NTLM' || 'WWW-Authenticate: Negotiate';
SECPKGNAMEHTTPWWWAUTHENTICATE: RawUTF8;
/// HTTP header pattern received for SSPI authentication 'AUTHORIZATION: NTLM ' || 'AUTHORIZATION: NEGOTIATE '
SECPKGNAMEHTTPAUTHORIZATION: RawUTF8;
......
implementation
procedure ForceNTLM(IsNTLM: boolean);
begin
if IsNTLM then begin
SECPKGNAMEINTERNAL := 'NTLM';
SECPKGNAMEHTTPWWWAUTHENTICATE := 'WWW-Authenticate: NTLM';
SECPKGNAMEHTTPAUTHORIZATION = 'AUTHORIZATION: NTLM ';
end else begin
SECPKGNAMEINTERNAL := 'Negotiate';
SECPKGNAMEHTTPWWWAUTHENTICATE := 'WWW-Authenticate: Negotiate';
SECPKGNAMEHTTPAUTHORIZATION = 'AUTHORIZATION: NEGOTIATE ';
end;
end;
initialization
ForceNTLM(False);
in case I know my server must not use kerberos I call forceNTLM(True) and everything is OK
Offline
So if my server not a windows service I can't authorize clients using windows credential.
You can use Kerberos after registering SPN. For example, if your server runs under mydomain\svruser account:
setspn -a http/myserver.mydomain.tld svruser
But generally adding ForceNTLM is a good idea. If domain not configured properly, Kerberos can be a problem. And forcing NTLM can help avoid some of the problems.
Offline
Oh. Thanks! I miss what I can use HTTP service name. But in all case this operation required Domain Admin right and as you sad domain sometimes not configured properly. Chaa, do you apply this patch? (SSPI is your's module)
Offline
I suspect the patch should be at higher level, and not changing any global variable.
If you have several clients with several servers at once, you can not mix the setting.
I do not like such global setting very much.
Offline
I see two ways:
Add ForceNTLM and mark in the documentation that it is a dirty hack, do not use it unless absolutely necessary.
Or add TSQLRestServerAuthenticationNTLM, TSQLRestServerAuthenticationKerberos and TSQLRestServerAuthenticationNegotiate
as child classes for TSQLRestServerAuthenticationSSPI.
We can use virtual class function for that purpose:
TSQLRestServerAuthenticationSSPI = class(TSQLRestServerAuthenticationSignedURI)
protected
class function SecPackageName: String; virtual;
end;
P.S.
The second approach will bring a lot of problems. For example, on the client and the server must be selected the same security package.
Last edited by Chaa (2013-08-09 10:40:36)
Offline
The second approach will bring a lot of problems. For example, on the client and the server must be selected the same security package.
It could be seen not as a problem, but as a feature.
Offline
mpv wrote:So if my server not a windows service I can't authorize clients using windows credential.
You can use Kerberos after registering SPN. For example, if your server runs under mydomain\svruser account:
setspn -a http/myserver.mydomain.tld svruser
But generally adding ForceNTLM is a good idea. If domain not configured properly, Kerberos can be a problem. And forcing NTLM can help avoid some of the problems.
We have a problems on some (several) productions - our customers have a big domain (1000+ users), but SPNs do not configured correctly. It is impossible to configure SPNs in a short tern, so we need to add a ForceNTLM. If there is no objection, we will commit changes to trunk. By default we keep Negotiate, so nothing will break the existed code..
Offline
You can force kerberos on client side:
TSQLRestClientURI.SetUser('', 'http/myserver.mydomain.tld');
Or you can use NTLM:
TSQLRestClientURI.SetUser('', '');
Or you can use NTLM with explicit user name and password:
TSQLRestClientURI.SetUser('mydomain\user2', 'secretpass');
If you do not pass SPN to client auth code, NTLM used automatically and server SPN does not checked.
Offline
You need ForceNTLM as run-time variable or compile-time directive?
Offline
As a run-time variable - we must configure this parameter in a different way for a different customers. We already did the modification as proposed here: http://synopse.info/forum/viewtopic.php?pid=8342#p8342 Can I commit it?
Offline
Can I commit it?
I think yes.
I will check it on my servers later.
Offline
Done. See [33c157472d]
Offline
Done. See 33c157472d
This version do not work if ForceNTLM on client is different from ForceNTLM on server.
I think that it's no good - we cannot change server configuration without changing each client settings.
My vesrion differs in the following cases:
- ForceNTLM forces NTLM authentication only for browser authenticaton.
- mORMot client stays use Negotiate and can choose from NTLM (SPN not specified) or Kerberos (SPN set).
- server detect Negotiate or NTLM requests and use appropriate SSPI package.
Offline
@Chaa - yes, you are right. Your solution is better. I checked - works perfectly in my case. So I'll commit.. Thnks!
Committed. See [41e5251a46]
Last edited by mpv (2016-08-31 12:38:46)
Offline
Looks like I copy a SynSSPIAuth.dcu instead of SynSSPIAuth.pas from my source control to fossil. Fixed by commit [f41a6331e4]. I'll think about how to modify a SourceCodeRep tool to prevent such situations in future
Offline
Introducing Kerberos support for mORMot on Linux.
Sergey - one of UnityBase team member adds a Kerberos support for mORMot server on Linux using GSS-API specification. Either mit-krb5 (MIT) or heimdal (Heimdal) can be used as library what implements a GSS-API.
Currently supported only on server and successfully tested on mORMot based server running on Linux Ubuntu and connected to Windows domain for both Windows and Linux browser client.
Since we do not use a thick clients for our server we do not have experience to implement this part in mORMot
Please, see a pull request #110
Offline
I think it is better to do so:
1. Add SynGSSAPI.pas as linux counterpart to SynSSPI.pas
2. Add ifdef Windows/Linux to SynSSPIAuth.pas and implement authentication using SynSSPI.pas or SynGSSAPI.pas
3. May be rename SynSSPIAuth.pas to SynDomainAuth.pas or something else
4. Do not touch mORMot.pas
Some time ago I use krb5 library on linux, so I think I can help implementing Kerberos on linux.
And I recommend using Dovecot auth as reference:
https://github.com/dovecot/core/blob/ma … h-gssapi.c
Offline
I like Chaa approach here, especially not introducing a new unit - and we may keep the SynSSPI.pas unit name by now.
But it is additional work, in regard to the actual pull request, so any help is welcome!
Offline
@Chaa - yes, this will be good to do such, and we thought about it, but I don't understand how to implement GSSAPI auth without touching mORMot.pas at all.
The only way I see now is to keep SynGSSAPI, introduce a new class TSQLRestServerAuthenticationKerberos in mORMot.pas and not touch TSQLRestServerAuthenticationSSPI
@ab - Sergey will modify SynGSSAPI to use a dynamic linking.
Offline
@chaa - we use a nginx kerberos authentication as a reference implementation. I think nging this is a good source of truth
@ab - your comments about consts and dynamically library loading are fixed. Can I merge pull request to trunk? Or better to create a brunch for it?
Offline
Merged GSSAPI pull request. Sample "04 - HTTP Client-Server\Project04Server.lpi" will successfully authenticate a browser clients in case SPN is configured on Linux server.
Offline
My test environment: Ubuntu 18.04 x86_64, NewPascal 1.0.55 (FPC 3.1.1, Lazarus 1.9.0).
When I run test app I get SIGSEGV on line:
PPointer(@gss_indicate_mechs)^ := GetProcAddress(handle, 'gss_indicate_mechs');
Looks like as this statement does not work as expected under FPC. So I change it to:
gss_indicate_mechs := GetProcAddress(handle, 'gss_indicate_mechs');
And now statement works.
On assembler we see an error: unnecessary level of indirection.
fpc_error1.png
I'm not familiar with FPC and I do not know how to correctly fix this error.
Сontinue testing...
Offline
Also, unit finalization code wrong:
type
PGSSAPIFunctions = ^TGSSAPIFunctions;
TGSSAPIFunctions = record ... end;
var
GSSAPI: PGSSAPIFunctions;
begin
New(GSSAPI);
...
FreeAndNil(GSSAPI); // FreeAndNil calls TObject destructor
end;
So, finalization code should be:
finalization
if GSSAPI<>nil then begin
FreeLibrary(GSSAPI^.gsslib);
Dispose(GSSAPI);
GSSAPI := nil;
end;
end.
Offline
Yes, you are right about finalization. Thanks! See [d7afff6e]
Offline
https://github.com/synopse/mORMot/pull/117
This is a feature-rich GSSAPI authentication implementation.
Can work (I hope) in any direction (with proper configuration):
Windows Client -> Windows Server
Windows Client -> Linux Server
Linux Client -> Windows Server
Linux Client -> Linux Server
Windows Browser -> Windows Server
Windows Browser -> Linux Server
Linux Browser -> Windows Server
Linux Browser -> Linux Server
Of course, we need to test it (especially on Linux).
Indeed, there are still some problems, perhaps a discussion is required:
1. Different user names: 'domain\user1' for Windows and 'user1@DOMAIN.TLD' on Linux.
We can use 'user1@DOMAIN.TLD' on Windows too, but this is backward incompatible and '\' is really rare used for average LogonName, but '@' is commonly used.
2. No space for SPN if we use authentification with domain username and password, and new function ClientForceSPN introduced for this.
3. in mORMot.pas there is call to LoadGSSAPI, since dynamically load library in server thread pool is a bad idea (race condition).
4. Linux client can't work without SPN and it is required. In theory, we can create SPN from server hostname, as browser did it.
Offline
Great to see progresses here!
1. We could simply identify and compute the expected user name on Linux server, and convert 'domain\user1' to 'user1@DOMAIN.TLD' on this platform.
I mean, use the Windows convention on the client side on all platforms.
2. I'm not sure I understand this point.
3. The load library could be made thread-safe using a critical section, but perhaps I don't have any problem doing it in the initialization - or perhaps on the TSQLRest.Create only.
4. I'm not sure I understand this point.
Offline
1. We could simply identify and compute the expected user name on Linux server, and convert 'domain\user1' to 'user1@DOMAIN.TLD' on this platform.
I mean, use the Windows convention on the client side on all platforms.
I added user name conversion - 'domain\user1' used on all platforms.
For clarification: our goal is to store same value in TSQLAuthUser.LogonName, and no need to modify database when server moved from Windows to Linux or vice versa.
2. I'm not sure I understand this point.
On Windows we can omit service SPN in client at all. In that case NTLM is used and client not check service identity - all works fine.
On Linux if I omit service SPN then GSSAPI return error 'A required input parameter could not be read'.
And we must always specify SPN in aPassword parameter for SetUser.
But there exists other option: authenticate with manually specified domain user name and clear text password.
In this case aPassword (in SetUser) used for password, so no space for SPN.
Function ClientForceSPN was added for that use case.
4. I'm not sure I understand this point.
Browsers can create SPN from hostname automatically.
After some investigation I realized that it is difficult: we need DNS request, consider the presence of a proxy, standard/non-standerd port, case sensivity on Linux, and so on (chromium, if someone is interested).
it's easier to specify the correct SPN manually.
P.S.
mpv, waiting for your opinion.
Offline
Chaa, I will verify your implementation with browser based clients, may be tomorrow (I'm overloaded a little). For a non-browser clients IMHO SPN should be created manually. In any case all the big companies switched their Domains from NTLM to Kerberos due to HUGE security holes in NTLM and administrators know how to create SPN's
Offline
2mpv
To use same value for TSQLAuthUser.LogonName on all platforms user name changed from 'username@MYDOMAIN.TLD' to 'MYDOMAIN\username'.
You used in previous version 'username@MYDOMAIN.TLD'.
So for you added function ServerForceNativeUserName. If you do not use GSSAPI in production yet, it may be removed.
Offline
Hi, Chaa
MPV is too busy right now with other tasks so asked me to verify your patch in our environment.
Everything works fine!
The only thought worth to mention is a conversion between Kerberos username and NT username is not as easy in common case. For example, I've seen a domain where NT style name was ABCCORP, while the canonical name - CORP.ABC.COM.UA. I suppose it is hard to automatically convert names, so it could be useful to introduce some "dictionary" and register name pairs while bootstrapping.
Offline
I suppose it is hard to automatically convert names, so it could be useful to introduce some "dictionary" and register name pairs while bootstrapping.
NT4 (or NetBIOS) domain names are outdated now, so I think we can choose a different way: the fully qualified domain name.
Currently used names:
DOMAIN\user1
ABCCORP\user1
New names:
DOMAIN.TLD\user1
CORP.ABC.COM.UA\user1
For backward compatibility we must add ServerForceShortDomain.
Offline
After some investigation I see that fully qualified domain name makes problem too.
For example, QueryContextAttributes(SECPKG_ATTR_NAMES) return name only in short format, and TranslateName(NameDnsDomain) returns error in my domain. I can avoid this by using TranslateName(NameUserPrincipal) and convert user name as in SynGSSAPIAuth.pas, but it is strange and is difficult to predict the consequences.
So, for maximum backward compatibility, I propose to leave Windows part as it is.
And in Linux part add dictionary to convert FQDN to short NetBIOS names (used only in rare cases, if automatic conversion do it wrong).
var
/// Dictionary for converting fully qualified domain names to NT4-style NetBIOS names
// - to use same value for TSQLAuthUser.LogonName on all platforms user name
// changed from 'username@MYDOMAIN.TLD' to 'MYDOMAIN\username'
// - when converting fully qualified domain name to NT4-style NetBIOS name
// ServerDomainMap first checked. If domain name not found, then it's truncated on first dot,
// e.g. 'user1@CORP.ABC.COM' changed to 'CORP\user1'
// - you can change domain name conversion by registering names at server startup, e.g.
// ServerDomainMap.Add('CORP.ABC.COM', 'ABCCORP') change conversion for previuos
// example to 'ABCCORP\user1'
// - use only if automatic conversion (truncate on first dot) do it wrong
ServerDomainMap: TSynNameValue;
Offline
Merged to both repos (github & fossil). See [2df853a7e1]. Very valuable contribution, thanks to Chaa & ssoftpro!
Offline