jcmturner / gokrb5

Pure Go Kerberos library for clients and services
Apache License 2.0
729 stars 249 forks source link

Getting KDC_ERR_S_PRINCIPAL_UNKNOWN error #367

Open brlbil opened 4 years ago

brlbil commented 4 years ago

Hi

Thanks for the library. I have only a basic understanding of kerberos. I looked all over the examples and issues to make this work. But I could not make it work. I am trying to authenticate to a service using keytab for the host account, not a logged-in user. Basically I am trying the procedure below in golang. Domain names are changed.

kinit -k -t /etc/krb5.keytab host/`hostname --fqdn`

curl  -u : --negotiate https://acxyz.pklngnt-zum.example.com

The code that is run is below.

        kt, err := keytab.Load(ktPath)
    if err != nil {
        return nil, fmt.Errorf("Load keytap %s failed: %w", ktPath, err)
    }

    cfg, err := config.Load(cfgPath)
    if err != nil {
        return nil, fmt.Errorf("Load config %s failed: %w", cfgPath, err)
    }

    l := log.New(os.Stderr, "krb5-client", log.Lshortfile)
    c := client.NewWithKeytab(
                  "host/xxxx.ipa.pklcst-zum.example.com",
                  "IPA.PKLCST-ZUM.EXAMPLE.COM", 
                  kt, cfg, client.DisablePAFXFAST(true), client.Logger(l))

    err = c.Login()
    if err != nil {
        return nil, fmt.Errorf("Login failed: %w", err)
    }
        defer c.Destroy()

        spn := fmt.Sprintf("HTTP/%s", "acxyz.pklngnt-zum.example.com")

    spnego.NewClient(k.c, http.DefaultClient, spn).Do(req)

I got the below error

could not initialize context: [Root cause: KDC_Error] KDC_Error: TGS Exchange Error: kerberos error response from KDC when requesting for HTTP/acxyz.pklngnt-zum.example.com: KRB Error: (7) KDC_ERR_S_PRINCIPAL_UNKNOWN Server not found in Kerberos database - LOOKING_UP_SERVER

Below are the logs from client

krb5-clientsettings.go:67: TGT session added for IPA.PKLCST-ZUM.EXAMPLE.COM (EndTime: 2020-02-08 20:37:43 +0000 UTC)
krb5-clientsettings.go:67: using SPN HTTP/acxyz.pklngnt-zum.example.com
krb5-clientsettings.go:67: client destroyed

Below are the klist -k /etc/krb5.keytab

Keytab name: FILE:/etc/krb5.keytab
KVNO Principal
---- --------------------------------------------------------------------------
   1 host/xxxx.ipa.pklcst-zum.example.com@IPA.PKLCST-ZUM.EXAMPLE.COM
   1 host/xxxx.ipa.pklcst-zum.example.com@IPA.PKLCST-ZUM.EXAMPLE.COM

Part of /etc/krb5.conf file is below

[domain_realm]
  .ipa.pklcst-zum.example.com = IPA.PKLCST-ZUM.EXAMPLE.COM
  ipa.pklcst-zum.example.com = IPA.PKLCST-ZUM.EXAMPLE.COM
  xxxx.ipa.pklcst-zum.example.com = IPA.PKLCST-ZUM.EXAMPLE.COM

[capaths]
  IPA.PKLCST-ZUM.EXAMPLE.COM = {
    PKLCST-ZUM.EXAMPLE.COM = .
    PKLNGNT-ZUM.EXAMPLE.COM = .
  }

I also checked the DNS resolution of acxyz.pklngnt-zum.example.com, and reverse lookup for the ip and it resolves to the same name. But I am not sure it is related.

go version go1.13 library version github.com/jcmturner/gokrb5/v8 v8.0.1

jcmturner commented 4 years ago

Hi @brlbil

The error KDC_ERR_S_PRINCIPAL_UNKNOWN Server not found in Kerberos database means that you don't have an SPN registered in the KDC for HTTP/acxyz.pklngnt-zum.example.com

This is not the same as a DNS entry. If you are using Active Directory this may help: https://docs.microsoft.com/en-gb/archive/blogs/autz_auth_stuff/what-is-a-spn-and-why-should-you-care

brlbil commented 4 years ago

Hi @jcmturner

After some strace to see what curl does I found out that curl was using credentials cache to connect to the service. I could make this work by using CCache.

..........

Apparently, I spoke too soon. Earlier it worked because the library was using a ticket issued by using curl. But it cannot get a valid ticket on its own. Let me clarify

When I ran command below and run my code later it worked.

KRB5CCNAME=FILE:/tmp/krb5cc_test kinit -k -t /etc/krb5.keytab host/`hostname --fqdn`
KRB5CCNAME=FILE:/tmp/krb5cc_root curl -k -u : --negotiate https://acxyz.pklngnt-zum.example.com

klist -c /tmp/krb5cc_test
Ticket cache: FILE:/tmp/krb5cc_test
Default principal: host/xxxx.ipa.pklcst-zum.example.com@IPA.PKLCST-ZUM.EXAMPLE.COM

Valid starting       Expires              Service principal
02/10/2020 10:28:11  02/10/2020 20:28:11  krbtgt/IPA.PKLCST-ZUM.EXAMPLE.COM@IPA.PKLCST-ZUM.EXAMPLE.COM
    renew until 02/11/2020 10:28:11
02/10/2020 10:29:27  02/10/2020 20:28:11  krbtgt/PKLNGNT-ZUM.EXAMPLE.COM@IPA.PKLCST-ZUM.EXAMPLE.COM
    renew until 02/11/2020 10:28:11
02/10/2020 10:28:43  02/10/2020 20:28:11  HTTP/acxyz.pklngnt-zum.example.com@PKLNGNT-ZUM.EXAMPLE.COM
    renew until 02/11/2020 10:28:11

and my code is minus error handling

cache, err := credentials.LoadCCache(cacheFile)
cfg, err := config.Load(confFile)
l := log.New(os.Stderr, "krb5-client", log.Lshortfile)
c, err := kclient.NewFromCCache(cache, cfg, kclient.DisablePAFXFAST(true), kclient.Logger(l))
c.Login()
sc := spnego.NewClient(k.c, http.DefaultClient, "")
// tansport not copied by default, so if service has unsigned certs it fails.
sc.Transport = c.Transport
sc.Do(req)

The second try after I removed the cache file I only ran kinit then my code then it failed.

KRB5CCNAME=FILE:/tmp/krb5cc_test kinit -k -t /etc/krb5.keytab host/`hostname --fqdn`

klist -c /tmp/krb5cc_test
Ticket cache: FILE:/tmp/krb5cc_test
Default principal: host/xxxx.ipa.pklcst-zum.example.com@IPA.PKLCST-ZUM.EXAMPLE.COM

Valid starting       Expires              Service principal
02/10/2020 14:50:15  02/11/2020 00:50:15  krbtgt/IPA.PKLCST-ZUM.EXAMPLE.COM@IPA.PKLCST-ZUM.EXAMPLE.COM
    renew until 02/11/2020 14:50:15

//got error

krb5-clientsettings.go:67: using SPN HTTP/acxyz.pklngnt-zum.example.com
could not initialize context: [Root cause: KDC_Error] KDC_Error: TGS Exchange Error: kerberos error response from KDC when requesting for HTTP/acxyz.pklngnt-zum.example.com: KRB Error: (7) KDC_ERR_S_PRINCIPAL_UNKNOWN Server not found in Kerberos database - LOOKING_UP_SERVER

I could not figure out what curl does differently than this library, or am I setting it wrong?

Thanks

jcmturner commented 4 years ago

Can you post your krb5.conf that you are using on this line:

cfg, err := config.Load(confFile)

I also see you are passing k.c to the spnego.NewClient function. What is this as it is not declared in the code you have posted.

brlbil commented 4 years ago

When I look into the code more dept, I realized that a TGT ticket for IPA.PKLCST-ZUM.EXAMPLE.COM is gotten, but there was no TGT for PKLNGNT-ZUM.EXAMPLE.COM, thus service ticket for HTTP/acxyz.pklngnt-zum.example.com was also failing.

I made this work last week in a very hacky way. I got the TGT for PKLNGNT-ZUM.EXAMPLE.COM and also Service ticket for HTTP/acxyz.pklngnt-zum.example.com like below.

TGSREQGenerateAndExchange(spn, kdcRealm, tgt, key, false)

Then if I make get request it worked.

Later I found the real cause for it. When we make a request GetServiceTicket function is called, in the function, a realm is resolved from config and later it will get a ticket for the realm if not already in the session.

func (cl *Client) GetServiceTicket(spn string) .....
......
realm := cl.Config.ResolveRealm(princ.NameString[len(princ.NameString)-1])

tgt, skey, err := cl.sessionTGT(realm)
..........

But in our configuration file, there is no domain to realm mapping.

[domain_realm]
  .ipa.pklcst-zum.example.com = IPA.PKLCST-ZUM.EXAMPLE.COM
  ipa.pklcst-zum.example.com = IPA.PKLCST-ZUM.EXAMPLE.COM
  xxxx.ipa.pklcst-zum.example.com = IPA.PKLCST-ZUM.EXAMPLE.COM

[capaths]
  IPA.PKLCST-ZUM.EXAMPLE.COM = {
    PKLCST-ZUM.EXAMPLE.COM = .
    PKLNGNT-ZUM.EXAMPLE.COM = .
  }

I made a better workaround by simply adding a domain to realm mapping from code.

h := strings.SplitN(req.URL.Host, ".", 2)[1]
c.Config.DomainRealm["."+h] = strings.ToUpper(h)

Since curl works with this configuration, I suspect it uses [capaths] of the configuration. For reference link for [capaths] : https://web.mit.edu/kerberos/krb5-1.5/krb5-1.5.4/doc/krb5-admin/capaths.html

The question is should the library try to cross-realm authentication if resolution from domain to the realm is failed? As I said in the first comment I do not know Kerberos that well so I am not sure.

If I am not mistaken [capaths] is not even parsed in the config.

Regards Birol