go-ldap / ldap

Basic LDAP v3 functionality for the GO programming language.
Other
2.25k stars 355 forks source link

Add support for SSPI GSSAPI SASL mechanism bind #402

Closed FlipB closed 1 year ago

FlipB commented 2 years ago

This change implements GSSAPI SASL bind using a given GSSAPI (Kerberos) client.

GSSAPI client interface (ldap.GSSAPIClient) is inspired by golang.org/x/crypto/ssh.GSSAPIClient with the following definition:

type GSSAPIClient interface {
    InitSecContext(target string, token []byte) (outputToken []byte, needContinue bool, err error)
    NegotiateSaslAuth(token []byte, authzid string) ([]byte, error)
    DeleteSecContext() error
}

A client using Windows SSPI is included (gssapi.SSPIClient). This client allows Windows clients to use current process' credentials for bind authentication.

This implementation does not support SASL security layers. Using this implementation (even with TLS) may be a bad idea, see https://wiki.samba.org/index.php/Configuring_LDAP_over_SSL_(LDAPS)_on_a_Samba_AD_DC.

Example:

package main

import (
    "fmt"
    "log"

    "github.com/go-ldap/ldap/v3"
    "github.com/go-ldap/ldap/v3/gssapi"
)

func main() {
    adHost := "addc.domainname.com"
    baseDnToQuery := "dc=domainname,dc=com"
    query := "(samaccountname=*bob*)"

    kerberosClient, err := gssapi.NewSSPIClient()
    if err != nil {
        log.Fatalf("error getting SSPI Kerberos client: %v", err)
    }
    defer kerberosClient.Close()

    // Connect to Active directory at `adHost`
    conn, err := ldap.DialURL(fmt.Sprintf("ldap://%s:389", adHost))
    if err != nil {
        log.Fatalf("error connecting to AD: %v", err)
    }

    // Bind using supplied kerberosClient against `adHost`
    err = conn.GSSAPIBind(kerberosClient, fmt.Sprintf("ldap/%s", adHost), "")
    if err != nil {
        log.Fatalf("error performing GSSAPI bind: %w", err)
    }

    // Successfully bound as current process' user.
    q := ldap.NewSearchRequest(baseDnToQuery,
        ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
        query, []string{}, nil)
    result, err := conn.Search(q)
    if err != nil {
        log.Fatalf("error querying AD: %v", err)
    }

    log.Printf("Result: %+v\n", *result)
}

This is a partial implementation for #115 (Since only Windows Kerberos client implementation is included).

tooptoop4 commented 2 years ago

@FlipB do u have example have how to use this feature? also how to retrieve username if auth was successful?

FlipB commented 2 years ago

@tooptoop4 I have included an example above.

I haven't looked at any way to retreive the authenticated users name from ldap, but on the client it should be the same user as returned from eg. os/user's Current().

cpuschma commented 1 year ago

Hi,

Apologies for the absence, a lot has happened in the last few days that needed my full attention. I have scrolled through the source code and so far have not found any changes that I would have to reject. When I'm in the office next week I'll check the functionality, I don't have an Active Directory at hand right now.

Admittedly, I am not familiar with kerberos and gssapi. If someone who knows about it could contact me that would be cool to check if the implementation is correct.

Until then :)

FlipB commented 1 year ago

I tested your PR with the example you provided, it worked as intended! Great work! Can you please add an test as an example for users who want to use this feature? The functions and the parameters are really self explainatory

Good to hear it worked with your setup as well, I've only tested against one Active directory instance.

I've added an example.

cpuschma commented 1 year ago

Might be better to expose more details from the GSSAPI implementation (eg. Wrap, Unwrap) then the interface could be used to implement security layers. Problem is that a lot of details has to be exposed over the interface if the negotiation is to be implemented on the ldap package side (max buffer sizes etc.).

I think we can merge the PR in the current version and expose certain methods in later releases if we want to. Again, thank you for this PR :)