-
Notifications
You must be signed in to change notification settings - Fork 365
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
[bugfix] Added APOptions []int in client.GSSAPIBindRequest(...) and client.InitSecContext(...), fixes #536 #537
Conversation
3a20a5c
to
fe5ed31
Compare
Hi @p0dalirius, thank you very much for your PR and the work you put into debugging this issue (#536)! My strengths are not at all in Kerberos and my knowledge of the protocol is practically zero, so thank you again! But: Modifying the signature of an existing function will definitely interfere with existing code. Even the addition of changes that are only noticed by linters has caused trouble in the past (#463). If there is really no other way to implement Kerberos authentication, I would be willing to do so and include it in the next release, but only if no one objects. @go-ldap/committers thoughts? |
@cpuschma, before merging, It would be nice if someone can try to connect to a UNIX ldap server with GSSAPI to confirm that it still works with this fix I will try to setup one and test it |
Using functional options instead of a slice of ints should not break existing implementations |
Could someone please advise if this will be merged soon? :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of the difficulties with this PR is the lack of GSSAPI knowledge by many of the maintainers - if someone with GSSAPI experience could weigh in that would be helpful.
That being said there are some things that should be done differently, and I've tried to point out some of them in this review.
I like the idea of using functional options to avoid future breaking changes, but I'm not sure there's really a need for it in this particular case.
v3/gssapi/client.go
Outdated
@@ -99,7 +103,7 @@ func (client *Client) DeleteSecContext() error { | |||
// InitSecContext initiates the establishment of a security context for | |||
// GSS-API between the client and server. | |||
// See RFC 4752 section 3.1. | |||
func (client *Client) InitSecContext(target string, input []byte) ([]byte, bool, error) { | |||
func (client *Client) InitSecContext(target string, input []byte, APOptions []int) ([]byte, bool, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should not break the API.
I suggest adding a new func (*Client) InitSecContextWithOptions(string, []byte, []int) ([]byte, bool, error)
and then modify the internals of the original InitSecContext
to simply call the new InitSecContextWithOptions
, passing in suitable defaults.
The earlier commits would have broken the client in (I think it makes sense to add asserts that both clients implement the interface to make breaking changes less likely, although you would still have to build for Windows (looks like CI only builds for linux) to be sure.) |
Hi! I also have a problem to connect to LDAP with Kerberos from a linux machine. After applying the fix from this pull request, I got another error:
As I understand it is because that go-ldap doesn't support channel binding. |
@ivan-san Yes, this is likely due to missing channel bindings. Unfortunately channel bindings cannot easily be implemented in However, this error is very likely not related to #537 in any way. |
It would be great to see it! Thanks a lot! |
@ivan-san It is published now: https://github.com/RedTeamPentesting/adauth |
I did some testing on Active Directory and OpenLDAP directory servers and couldn't find any problems. @p0dalirius, can you update your branch and take a look at John Weldons comment so we can start merging this? We got rid of the copy of the code outside the v3 directory, so those files can be safely disregarded Thank you alot for you hard work figuring this problem out! |
a55dbd6
to
b497d00
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added GSSAPIClient.InitSecContextWithOptions(...), fixes #536
Hi everyone, I have updated my changes to resolve conflicts and follow @johnweldon's advice |
…ry' of github.com:p0dalirius/ldap into fix-ldap-gssapi-kerberos-auth-on-windows-active-directory
Hi everyone, I have updated my changes to resolve conflicts and follow @johnweldon's advice To sum up: GSSAPIClientThe code of the original func (client *Client) InitSecContextWithOptions(target string, input []byte, APOptions []int) ([]byte, bool, error) And the original func (client *Client) InitSecContext(target string, input []byte) ([]byte, bool, error) {
return client.InitSecContextWithOptions(target, input, []int{})
} ConnThe code of the original func (l *Conn) GSSAPIBindRequestWithAPOptions(client GSSAPIClient, req *GSSAPIBindRequest, APOptions []int) error And the original // GSSAPIBindRequest performs the GSSAPI SASL bind using the provided GSSAPI client.
func (l *Conn) GSSAPIBindRequest(client GSSAPIClient, req *GSSAPIBindRequest) error {
return l.GSSAPIBindRequestWithAPOptions(client, req, []int{})
} This prevents breaking changes in the API and introduces a way to use APOptions for Kerberos authentication Example code with this new functions:package main
import (
"encoding/hex"
"fmt"
"log"
"strings"
"time"
"github.com/go-ldap/ldap/v3"
"github.com/jcmturner/gokrb5/v8/iana/flags"
"github.com/go-ldap/ldap/v3/gssapi"
"github.com/jcmturner/gokrb5/v8/client"
"github.com/jcmturner/gokrb5/v8/config"
)
func main() {
fqdnLDAPHost := "SRV-DC01.lab.local"
baseDN := "DC=LAB,DC=local"
realm := "lab.local"
realm = strings.ToUpper(realm)
// This is always in uppercase, if not we get the error:
// error performing GSSAPI bind: [Root cause: KRBMessage_Handling_Error]
// | KRBMessage_Handling_Error: AS Exchange Error: AS_REP is not valid or client password/keytab incorrect
// | | KRBMessage_Handling_Error: CRealm in response does not match what was requested.
// | | | Requested: lab.local;
// | | | Reply: lab.local
// | 2024/10/08 15:36:16 error querying AD: LDAP Result Code 1 "Operations Error": 000004DC: LdapErr: DSID-0C090A5C,
// | comment: In order to perform this operation a successful bind must be completed on the connection., data 0, v4563
username := "Administrator"
// error performing GSSAPI bind: [Root cause: KDC_Error] KDC_Error: AS Exchange Error: kerberos error response from KDC:
// KRB Error: (6) KDC_ERR_C_PRINCIPAL_UNKNOWN Client not found in Kerberos database
// KDC_ERR_C_PRINCIPAL_UNKNOWN (error code 6) for these means that the domain controller to which the request
// was made does not host the account and the client should choose a different domain controller.
// src: https://learn.microsoft.com/en-us/troubleshoot/windows-server/certificates-and-public-key-infrastructure-pki/kdc-err-c-principal-unknown-s4u2self-request
// ==> This means this username does not exist
password := "Admin123!"
servicePrincipalName := fmt.Sprintf("ldap/%s", fqdnLDAPHost)
krb5Conf := config.New()
// LibDefaults
krb5Conf.LibDefaults.AllowWeakCrypto = true
krb5Conf.LibDefaults.DefaultRealm = realm
krb5Conf.LibDefaults.DNSLookupRealm = false
krb5Conf.LibDefaults.DNSLookupKDC = false
krb5Conf.LibDefaults.TicketLifetime = time.Duration(24) * time.Hour
krb5Conf.LibDefaults.RenewLifetime = time.Duration(24*7) * time.Hour
krb5Conf.LibDefaults.Forwardable = true
krb5Conf.LibDefaults.Proxiable = true
krb5Conf.LibDefaults.RDNS = false
krb5Conf.LibDefaults.UDPPreferenceLimit = 1 // Force use of tcp
krb5Conf.LibDefaults.DefaultTGSEnctypes = []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "arcfour-hmac-md5"}
krb5Conf.LibDefaults.DefaultTktEnctypes = []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "arcfour-hmac-md5"}
krb5Conf.LibDefaults.PermittedEnctypes = []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "arcfour-hmac-md5"}
krb5Conf.LibDefaults.PermittedEnctypeIDs = []int32{18, 17, 23}
krb5Conf.LibDefaults.DefaultTGSEnctypeIDs = []int32{18, 17, 23}
krb5Conf.LibDefaults.DefaultTktEnctypeIDs = []int32{18, 17, 23}
krb5Conf.LibDefaults.PreferredPreauthTypes = []int{18, 17, 23}
// Realms
krb5Conf.Realms = append(krb5Conf.Realms, config.Realm{
Realm: realm,
AdminServer: []string{fqdnLDAPHost},
DefaultDomain: realm,
KDC: []string{fmt.Sprintf("%s:88", fqdnLDAPHost)},
KPasswdServer: []string{fmt.Sprintf("%s:464", fqdnLDAPHost)},
MasterKDC: []string{fqdnLDAPHost},
})
// Domain Realm
krb5Conf.DomainRealm[strings.ToLower(realm)] = realm
krb5Conf.DomainRealm[fmt.Sprintf(".%s", strings.ToLower(realm))] = realm
printKrb5Conf(krb5Conf)
// Connect to LDAP server
bindString := fmt.Sprintf("ldaps://%s:636", fqdnLDAPHost)
ldapConnection, err := ldap.DialURL(
bindString,
ldap.DialWithTLSConfig(
&tls.Config{
InsecureSkipVerify: true,
},
),
)
if err != nil {
log.Printf("[error] ldap.DialURL(\"%s\"): %s\n", bindString, err)
return
} else {
log.Printf("[debug] ldap.DialURL(\"%s\"): success\n", bindString)
}
ldapConnection.Debug = true
// Initialize kerberos client
// Inspired from: https://github.com/go-ldap/ldap/blob/06d50d1ad03bcd323e48f2fe174d95ceb31b8b90/v3/gssapi/client.go#L51
kerberosClient := gssapi.Client{
Client: client.NewWithPassword(
username,
realm,
password,
krb5Conf,
// Active Directory does not commonly support FAST negotiationso you will need to disable this on the client.
// If this is the case you will see this error: KDC did not respond appropriately to FAST negotiation
// https://github.com/jcmturner/gokrb5/blob/master/USAGE.md#active-directory-kdc-and-fast-negotiation
client.DisablePAFXFAST(true),
),
}
defer kerberosClient.Close()
// Initiating ldap GSSAPIBind
err = ldapConnection.GSSAPIBindRequestWithAPOptions(
&kerberosClient,
&ldap.GSSAPIBindRequest{
ServicePrincipalName: servicePrincipalName,
AuthZID: "",
},
[]int{flags.APOptionMutualRequired},
)
if err != nil {
log.Printf("[error] ldapConnection.GSSAPIBind(): %s\n", err)
return
} else {
log.Printf("[debug] ldapConnection.GSSAPIBind(): success\n")
}
// Successfully bound
searchRequest := ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
"(objectClass=user)",
[]string{"distinguishedName"},
nil,
)
ldapResults, err := ldapConnection.SearchWithPaging(searchRequest, 1000)
if err != nil {
log.Fatalf("[error] ldapConnection.Search(): %v\n", err)
return
} else {
log.Printf("[debug] ldapConnection.Search(): success\n")
}
for _, entry := range ldapResults.Entries {
fmt.Printf(" - %s", entry.DN)
}
log.Printf("[debug] All done!\n")
}
} @cpuschma I think everything is now in order, do you see anything else to change? Best regards, |
@p0dalirius LGTM! Thank you again for your changes! |
Fixing Kerberos authentication due to missing APOption "MutualRequired"
Overview of the fix
In
client.InitSecContext(...)
I have changed the
client.InitSecContext(...)
prototype from:to:
to be able to pass specific flags through the
APOptions []int
to the call tospnego.NewKRB5TokenAPREQ(client.Client, tkt, ekey, gssapiFlags, APOptions)
In
client.GSSAPIBind(...)
I have left this function as it was, so it can be used in the generic case of GSSAPI authentication without the need to pass specific AP Options flags in parameters.
In
client.GSSAPIBindRequest(...)
I have changed the
client.GSSAPIBindRequest(...)
prototype from:to
Example of a working code for Kerberos authentication
Summary
These
APOptions []int
can now be set when callingclient.GSSAPIBindRequest(...)
, which will then pass it to the underlyingclient.InitSecContext(...)
, which will then be processed in the call tospnego.NewKRB5TokenAPREQ(client.Client, tkt, ekey, gssapiFlags, APOptions)
Best Regards,