Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement NTLM Authentication #402

Closed
Badgerati opened this issue Nov 5, 2019 · 30 comments · Fixed by #1445
Closed

Implement NTLM Authentication #402

Badgerati opened this issue Nov 5, 2019 · 30 comments · Fixed by #1445

Comments

@Badgerati
Copy link
Owner

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
Copy link
Owner Author

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
Copy link
Contributor

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
Copy link
Owner Author

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
Copy link
Contributor

ArieHein commented Mar 25, 2020

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

@Badgerati
Copy link
Owner Author

@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
Copy link

AndreasNick commented Aug 18, 2021

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
Copy link
Owner Author

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
Copy link
Contributor

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
Copy link
Owner Author

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
Copy link

AndreasNick commented Sep 17, 2021

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
Copy link
Contributor

ArieHein commented Sep 17, 2021

@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
Copy link
Contributor

ittchmh commented Sep 17, 2021

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
Copy link
Owner Author

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
Copy link
Contributor

ittchmh commented Sep 29, 2021

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

@Badgerati
Copy link
Owner Author

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
Copy link

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
Copy link

AndreasNick commented Dec 12, 2021

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
Copy link
Owner Author

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
Copy link
Contributor

ittchmh commented Dec 19, 2021

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/host.contoso.com@CONTOSO.COM /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.

@AndreasNick
Copy link

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
Copy link

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
Copy link
Contributor

ittchmh commented Dec 20, 2021

@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
Badgerati/Pode.Web#129 (comment)

@Badgerati
Copy link
Owner Author

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
Copy link
Contributor

ittchmh commented Dec 21, 2021

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
Copy link
Owner Author

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
Copy link
Contributor

ittchmh commented Dec 31, 2021

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

@webalexeu
Copy link

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

@RobinBeismann
Copy link
Contributor

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 added a commit that referenced this issue May 29, 2024
Badgerati added a commit that referenced this issue May 29, 2024
@Badgerati
Copy link
Owner Author

Badgerati commented May 29, 2024

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:

  • Kerberos.NET will be a dependency, and used within the Pode .NET listener to make DLL loading much simpler - this was likely the PS5.1 fix!
  • I've added Negotiate as an inbuilt authentication scheme for New-PodeAuthScheme
  • A keytab file is still required

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

@Badgerati Badgerati self-assigned this May 29, 2024
@Badgerati Badgerati added this to the 2.11.0 milestone May 29, 2024
@ittchmh
Copy link
Contributor

ittchmh commented Jul 24, 2024

Hi @Badgerati

I will test this on this or next weekends

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants