Pode icon indicating copy to clipboard operation
Pode copied to clipboard

Implement NTLM Authentication

Open Badgerati opened this issue 6 years ago â€Ē 30 comments

Related Issues

#398

Describe the Feature

Pode has support for Basic/Windows LDAP authentication, but is currently missing NTLM auth. Looking at it, it seems very similar to Basic Auth - but with an initial challenge request before the Auth Header.

This will also require the WWW-Authenticate header to be implemented on 401s.

Additional Context

Useful links:

  • http://davenport.sourceforge.net/ntlm.html#ntlmHttpAuthentication
  • https://stackoverflow.com/a/13960538/2956588

Badgerati avatar Nov 05 '19 17:11 Badgerati

With #433, I'm considering making this an extension module for Pode instead - as it has to very Windows only interop functions that are required ðŸĪ”

Badgerati avatar Dec 18 '19 16:12 Badgerati

I think it would make sense to drop this completely and have it handled by IIS on Windows instead, this avoids having too much Windows only code and uses builtin board functions available on Client and Server Windows Systems. I mean the website pretty clearly describes how this works with IIS, keeping it on a header based authentication makes it work with other reverse proxies handling preauthentication too. For example a Apache with mod_mellon handling SAML Authentication.

https://badgerati.github.io/Pode/Hosting/IIS/

RobinBeismann avatar Mar 23 '20 23:03 RobinBeismann

I was thinking the same thing last week when I looked back at this issue, as IIS will take care of all NTLM and Kerberos auth.

I'll add some notes to the IIS docs to reference NTLM/Kerberos auth (for anyone who searches the docs for them), and then close this issue.

Badgerati avatar Mar 24 '20 19:03 Badgerati

What if I want to host it on a non-windows machine. Seeing as pode is cross platform ?

ArieHein avatar Mar 25 '20 16:03 ArieHein

@ArieHein, this was an issue I was trying to find a solution to, but I couldn't find a decent way of doing NTLM/Kerberos on Linux. I managed to do the AD auth cross-plat due to OpenLDAP pretty much being a standard on most nix machines, but for NTLM I haven't found anything yet ðŸĪ”

I'll do the IIS docs and leave this issue open, because it would be nice to have a way of doing this without any extra hosting and cross-platform.

One wild and crazy idea I had, which would make xplat easier, and using the existing AD support, was to move the 16-byte challenge that the AD generates into Pode - and Pode generates the Type 2 401 challenge. Though I'm not sure how this would fair security wise, and it would basically turn it into Digest.

Badgerati avatar Mar 25 '20 19:03 Badgerati

I have just successfully tested Kerberos authentication via IIS and I am excited about how easy it is to use something like this thanks Pode now in PoSh :-). Now since a few months there is the Pode.Kestrel module. Now I just read that Kestrela can also do Kerberos. Is there with the module the possibility of a Kerberos authentication without IIS? https://stackoverflow.com/questions/53842990/windows-authentication-for-kestrel-hosted-in-windows-service Well, I read more and the statement is probably wrong. It should work with a component http.sys, but it does not belong to Kestrel https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/httpsys?view=aspnetcore-5.0

AndreasNick avatar Aug 18 '21 16:08 AndreasNick

Yeah, I imagine it'll be similar to how HttpListener handled it via HTTP.sys.

I want to add in Kerberos authentication support for Pode, without IIS, but I want it to be cross-platform. When I last looked trying to find useful resources was difficult, but perhaps that's changed since then ðŸĪ” When I get chance I'll have to have another look, see if I can find anything.

Badgerati avatar Aug 18 '21 17:08 Badgerati

I guess if you'd want to implement it cross-platform, then it'd need to utilize keytab files like apache does with Kerberos.

RobinBeismann avatar Aug 18 '21 18:08 RobinBeismann

So, I've had a crack at trying Kerberos.NET, brought up in #819 by @ittchmh, and using the middleware sample found here.

I can get the library loaded, and seemingly validating requests however, for me I only ever seem to use NTLM - it never wants to use Kerberos ðŸĪ” I'm not very well versed in the world of Kerberos, so I'm very more than likely doing something wrong somewhere haha 😂

If anyone has any ideas, or would love to have a crack too (above commit), it'd be more than appreciated 😄

I've setup an example at /examples/web-auth-kerberos.ps1, and it's using a custom auth scheme for now - I'm mulling over whether we should include Kerberos.NET within Pode, or as a separate Pode.Kerberos module.

Badgerati avatar Sep 16 '21 20:09 Badgerati

Maybe it's the SPN? There must be an account for the respective service (or for the computer) and for this account there must be a SPN for the destination address. For example for myserver.mydom.net and the AD account myservice the following command must be executed (I have not tested the following and these are not working examples):

Setspn -S http/myserver.mydom.net myservice

If the service runs under a computer account (Network Service):

Setspn -S http/myserver.mydom.net ADComputerAccount

https://petri.com/how-to-use-setspn-to-set-active-directory-service-principal-names-2 https://social.technet.microsoft.com/wiki/contents/articles/717.service-principal-names-spn-setspn-syntax.aspx

AndreasNick avatar Sep 17 '21 05:09 AndreasNick

@Badgerati Windows by default tries to use Kerberos, if it fails it falls back to NTLM, so what your seeing is a correct behavior.

ArieHein avatar Sep 17 '21 07:09 ArieHein

I will look on this soon, thanks Separate module would be better NTLM not secure and should be disabled where possible.

So, I've had a crack at trying Kerberos.NET, brought up in #819 by @ittchmh, and using the middleware sample [found here]

ittchmh avatar Sep 17 '21 09:09 ittchmh

Had another shot, didn't realise you had to connect via a valid hostname rather than IP address. This time it negotiated with Kerberos rather than falling back to NTLM 😄 Now facing a crypto error the with keytab, but getting further!

Badgerati avatar Sep 17 '21 20:09 Badgerati

I want to try Kerberos branch Is it possible to build module on local machine?

ittchmh avatar Sep 29 '21 18:09 ittchmh

Hey @ittchmh,

Yep, definitely possible! You'll need the Invoke-Build module, and then you should be able to run Invoke-Build Build from the root of the repo. That will install dotnet, and build the .NET Listener for you. You'll then be able to use and import the /src/Pode.psm1 file :)

Badgerati avatar Sep 29 '21 18:09 Badgerati

I wanted to test this branch once and created a buld. Presumably the password for the account under which the process is started (for me 'mydom\podeserviceaccount') must be inserted here:

$k = [Kerberos.NET.Crypto.KerberosKey]::new('<password>')

Furthermore, an SPN for port 8090 and the 'podeserviceaccount' must be set. This did not work at least at first. Did I forget something?

AndreasNick avatar Dec 12 '21 09:12 AndreasNick

I have now tried it with a keyfile, as found in the description of kerberos.net. Unfortunately without success. Shouldn't a request for authentication appear in every case? Does the "Kestrel Listener" have to be active? ` ktpass /princ HTTP/surandc@+++l:8090 /mapuser uran\podewebservice /pass U+++ /out sample.keytab /crypto all /PTYPE KRB5_NT_SRV_INST /mapop set

$kf = New-Object Kerberos.NET.Crypto.KeyTable([System.IO.File]::ReadAllBytes("C:\PodeKerberos\sample.keytab")) $k = [Kerberos.NET.Crypto.KerberosKey]::new($kf) ` In any case, it's an exciting topic. And it also works well via the IIS

AndreasNick avatar Dec 12 '21 16:12 AndreasNick

The password for [Kerberos.NET.Crypto.KerberosKey] that i used was the password for the user running the process, and had rights to AD. I believe I setup an SPN, and didn't have to do anything weird for it to work - maybe restart the machine possibly? ðŸĪ”

This should work with Pode on its own, no Kestrel needed. The example I was playing with above doesn't use sessions, so it should prompt for auth each time; I was testing using Invoke-RestMethod and -UseDefaultCredentials. I got to a point where it would try Kerberos, but failed on something with the keytab the library was making (though I didn't try making and using a keytab directly!).

Badgerati avatar Dec 15 '21 16:12 Badgerati

Hi! I was able to use keytab file for authentication and get kerberosIdentity object, Kerberos Authentication WORKS!

To create KeyTab file:

  • Create user pode.kerb
  • Allow Kerberos delegation for user on Delegation tab of user properties image
  • Create KeyTabfile ktpass /princ HTTP/[email protected] /mapuser CONTOSO\pode.kerb /pass Pa$$w0rD /out podekerb.keytab /kvno 0 /crypto all /PTYPE KRB5_NT_PRINCIPAL /mapop set
  • Register SPN setspn -a HTTP/host.contoso.com CONTOSO\pode.kerb

To Authenticate using Kerberos:

  • Pode Server must be running on host.contoso.com host
  • Configure Browser IE/Chrome/MS Edge for Kerberos Authentication on client PC
  • Open host.contoso.com:9090 from client PC

To get Authorization header: remove realm="user" after Negotiate image

Working code:

Start-PodeServer {
    Add-PodeEndpoint -Address * -Port 9090 -Protocol Http
    New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging -Levels Debug,Error,Informational,Warning,Verbose

    $scheme = New-PodeAuthScheme -Name 'Negotiate' -Custom -ScriptBlock {

        $kf = [Kerberos.NET.Crypto.KeyTable]::new([System.IO.File]::ReadAllBytes("D:\Projects\PodeServer\podekerb.keytab"))
        $kf | Out-PodeHost
        $header = Get-PodeHeader -Name 'Authorization'
        $WebEvent.Request.Headers | Out-PodeHost
        if ($null -eq $header) {
            return @{
                Message = 'No Authorization header found'
                Code = 401
            }
        }

        $a = [Kerberos.NET.KerberosAuthenticator]::new($kf)
        $v = [Kerberos.NET.KerberosValidator]::new($kf)
        # $v | GM | Out-PodeHost
        $v.ValidateAfterDecrypt = 64
        $ticketBytes = [System.Convert]::FromBase64String($header.Split(" ")[1])
        $decrypted = $v.Validate($ticketBytes)
        "Decrypted ticket Result" | Out-PodeHost
        $decrypted.Result | Out-PodeHost
        #  "Decrypted ticket Ticket" | Out-PodeHost
        # $decrypted.Result.Ticket 
        # "Decrypted ticket SessionKey" | Out-PodeHost
        # $decrypted.Result.SessionKey | Out-PodeHost
        # "Decrypted ticket AuthorizationData" | Out-PodeHost
        # $decrypted.Result.AuthorizationData | Export-Clixml -Path .\decrypted.txt
        # $decrypted | Export-Clixml -Path .\decrypted.txt
        ##

        $i = $a.Authenticate($header)
        $i.Wait().Result | Out-PodeHost

        $principal = [System.Security.Claims.ClaimsPrincipal]::new($i.Result)
        $principal  | Out-PodeHost
        Set-PodeState -Name "claims" -Value $principal

        return @($principal)

    }

    $scheme | Add-PodeAuth -Name 'Login' -Sessionless -ScriptBlock {
        param($identity)
        $identity | Out-PodeHost
        return @{ User = $identity }
    }


    Add-PodeRoute -Method Get -Path '/' -Authentication Login -ScriptBlock {
        Write-PodeJsonResponse -Value @{ result = 'Hello' }
    }
}

Now as authentication works, Pode server must set WWW-Authenticate Header with proper values : Negotiate and Session Ticket I was able to retrieve Ticket Validation object, but could not retrieve session authentication token

Like here

Or like Pode on IIS, and IIS set Auth Headers image

@Badgerati please try to replicate! Let me know if you need help setting up Kerberos/KeyTab/Etc.

ittchmh avatar Dec 19 '21 19:12 ittchmh

That's very cool. Thank you for making the effort. I will test it again as soon as possible! It is already very good that this is now a successfully tested function with many possibilities for sso :-)

AndreasNick avatar Dec 20 '21 11:12 AndreasNick

It works perfectly for me that way too. This also provides the user who submits the ticket. I started the service as "administrator". A delegate for the kerberos user was not necessary. I also did not have to set an SPN. However, small configurations for the browser (Intranet Zone) This code snippet provides the user: ... $identity | Out-PodeHost ... Write-Verbose $($identity.CName.FullyQualifiedName) -Verbose

AndreasNick avatar Dec 20 '21 19:12 AndreasNick

@AndreasNick If SPN not set, authentication will fail if Pode on one host and client on another

Pode Host must be added to Intranet Zone for Kerberos

Please take a look on this issue as well https://github.com/Badgerati/Pode.Web/issues/129#issuecomment-995176310

ittchmh avatar Dec 20 '21 20:12 ittchmh

Hey @ittchmh,

That's amazing work! I'll look to test it as soon as possible 😄

For the last part, with regards to the headers, you can return custom headers from a scheme/auth in the hashtable returned. For example:

$header = Get-PodeHeader -Name 'Authorization'
if ($null -eq $header) {
    return @{
        Message = 'No Authorization header found'
        Code = 401
        Headers = @{
            'WWW-Authenticate' = 'Negotiate'
        }
    }
}

^ this will force the WWW-Authenticate header to what you want (ie: minus the realm flag).

And then the below should also let you set a session token as well, plus the Persistent-Auth header:

return @{
    User = $identity
    Headers = @{
        'Persistent-Auth' = 'true'
        'WWW-Authenticate' = "Negotiate $($someToken)"
    }
}

Badgerati avatar Dec 21 '21 16:12 Badgerati

Thanks @Badgerati, I spent almost all day on Sunday to get it working and figure out how to use this Net.Kerberos library ðŸĪŠ I gave up on getting decoded authentication token (Like you see on screenshot 'Pode on IIS') I will try again later, thank you for instructions how to set headers properly!

ittchmh avatar Dec 21 '21 16:12 ittchmh

Hi @ittchmh,

I managed to get some quick time to test the above, and can confirm it worked for me 😄. The only difficulty I had was that it would only work in PS6+, but not PS5.1, due to not finding a "System.Buffers" dll. Even the dll the library comes with didn't work in PS5.1, and it seems it could be a well know issue with .NET Framework 😕 Not the end of the world, and since this will be a separate module more than likely, I might have an idea to fix it.

I tried to find the session token as well, but with no luck 😂 I did test the headers trick about, and it worked for forcing WWW-Auth and not having a Realm (without having to change core Pode code).

Badgerati avatar Dec 31 '21 17:12 Badgerati

@Badgerati thanks for update, I will continue working on this on Sunday/Monday Happy New Year! 🎄⛄

ittchmh avatar Dec 31 '21 18:12 ittchmh

Hello, Can this solution work without generating keytab file? Thank you

webalexeu avatar Mar 23 '22 20:03 webalexeu

Hello, Can this solution work without generating keytab file? Thank you

No, this solution is platform agnostic, meaning it needs a common way to be able to request kerberos tickets and establish a trust with the client that authenticates.

What's the issue with the keytab file?

RobinBeismann avatar Mar 23 '22 20:03 RobinBeismann