Badgerati / Pode

Pode is a Cross-Platform PowerShell web framework for creating REST APIs, Web Sites, and TCP/SMTP servers
https://badgerati.github.io/Pode
MIT License
813 stars 92 forks source link

Implement NTLM Authentication #402

Open Badgerati opened 4 years ago

Badgerati commented 4 years ago

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:

Badgerati commented 4 years ago

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 ðŸĪ”

RobinBeismann commented 4 years ago

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/

Badgerati commented 4 years ago

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.

ArieHein commented 4 years ago

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

Badgerati commented 4 years ago

@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.

AndreasNick commented 2 years ago

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

Badgerati commented 2 years ago

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.

RobinBeismann commented 2 years ago

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

Badgerati commented 2 years ago

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.

AndreasNick commented 2 years ago

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

ArieHein commented 2 years ago

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

ittchmh commented 2 years ago

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]

Badgerati commented 2 years ago

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!

ittchmh commented 2 years ago

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

Badgerati commented 2 years ago

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 :)

AndreasNick commented 2 years ago

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 commented 2 years ago

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

Badgerati commented 2 years ago

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!).

ittchmh commented 2 years ago

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

To create KeyTab file:

To Authenticate using Kerberos:

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.

AndreasNick commented 2 years ago

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 commented 2 years ago

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

ittchmh commented 2 years ago

@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

Badgerati commented 2 years ago

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)"
    }
}
ittchmh commented 2 years ago

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!

Badgerati commented 2 years ago

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).

ittchmh commented 2 years ago

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

webalexeu commented 2 years ago

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

RobinBeismann commented 2 years ago

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?

Badgerati commented 1 month ago

I'm returning back to this again, in hopes of getting it out with v2.11.0 😃

The commits above are in a fresh new branch (Issue-402), as there have been a lot of changes since the original branch much further up. I'm still using Kerberos.NET, and I have seemingly got it to work with PS5.1 as well - as further up I hit some errors there.

A few notes/changes:

To setup the auth and use via route:

New-PodeAuthScheme -Negotiate -KeytabPath '.\user.keytab' | Add-PodeAuth -Name 'Login' -Sessionless -ScriptBlock {
    param($identity)
    return @{ User = $identity }
}

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

To generate the keytab file will need ktpass as outlined further up; to bring it back here as an example:

ktpass -princ HTTP/pode.example.com@EXAMPLE.COM -mapuser EXAMPLE\pode-user -pass Pa$$w0rD -out pode-user.keytab -kvno 0 -crypto all -ptype KRB5_NT_PRINCIPAL -mapop set

and if you need to use spn beforehand:

setspn -A HTTP/pode.example.com EXAMPLE\pode-user

I currently do not have the proper means to test this out unfortunately, so I'll require some assistance in doing so. I also want to see if a general ktpass command can be found like the above example, because I might put a wrapper helper function into Pode to help ðŸĪ”

To test this will require checking out the Issue-402 branch, installing InvokeBuild, and then running Invoke-Build Build at the root of the repo - this will install other dependencies and build Pode. The contents of the /src folder is then basically just the Pode module. (when you import it, use the .psm1 not the .psd1).

ittchmh commented 9 hours ago

Hi @Badgerati

I will test this on this or next weekends