Skip to content

lifetime of credentials #33

Description

@stanislavlevin

It seems gssproxy doesn't expose lifetime of credentials or doesn't do it properly.

In IPA env(WSGI, GSS_USE_PROXY=yes) I inquire the lifetime of creds as:

store = {'ccache': '/run/ipa/ccaches/xxx'}
creds = gssapi.Credentials(usage="initiate", name=None, store=store)
print(creds.lifetime)

which always show the initial lifetime of credentials (in my example it was always 20) even the credentials are expired.

While the decrypted ccache

import gssapi

store = {'ccache': '/root/decryptedccache'}
creds = gssapi.Credentials(usage="initiate", name=None, store=store)
print(creds.lifetime)

shows the correct remaining lifetime of creds and raises with ExpiredCredentialsError on expiration.

Is such proxied lifetime's behaviour expected, bug or not implemented yet?

Activity

  1. added
    bugSomething isn't working
    on Jun 8, 2021
  2. simo5 commented on Jun 8, 2021

    @simo5
    Member

    I think it is a bug, we do not handle lifetime, that I can see at a glance.

  3. simo5 commented on Jun 8, 2021

    @simo5
    Member

    But I need to reprouce to figure out what is the issue.
    As the "encrypted" creds are special so I need to figure out if there is something we can do or it makes sense to do something about the user accessbile credentials.

    In either case I am surprised the mechglue interposer is not making a call to gssproxy to get the lifetime?
    @stanislavlevin could you turn on debugging in gssproxy and show me what calls are made over the wire with the first code snippet you posted?

  4. stanislavlevin commented on Jun 8, 2021

    @stanislavlevin
    ContributorAuthor

    Sure, gssproxy log for that steps:
    gssproxy.log

    [root@master1 /]# cat /etc/gssproxy/10-ipa.conf 
    #Installed and maintained by ipa update tools, please do not modify
    [service/ipa-httpd]
      mechs = krb5
      cred_store = keytab:/var/lib/ipa/gssproxy/http.keytab
      cred_store = client_keytab:/var/lib/ipa/gssproxy/http.keytab
      allow_protocol_transition = true
      allow_client_ccache_sync = true
      cred_usage = both
      euid = apache
    
    [service/ipa-api]
      mechs = krb5
      cred_store = keytab:/var/lib/ipa/gssproxy/http.keytab
      cred_store = client_keytab:/var/lib/ipa/gssproxy/http.keytab
      allow_constrained_delegation = true
      allow_client_ccache_sync = true
      cred_usage = initiate
      euid = ipaapi
    
    [service/ipa-sweeper]
      mechs = krb5
      cred_store = keytab:/var/lib/ipa/gssproxy/http.keytab
      socket = /var/lib/gssproxy/ipa_ccache_sweeper.sock
      euid = ipaapi
      cred_usage = initiate
    

    The overall process is:

    1. ipa krbtpolicy-mod --maxlife 20
    2. kdestroy && kinit
    3. I inject pdb_clone into https://github.com/freeipa/freeipa/blob/6f49cc06569f46c8830d57498421a7e6fd9e4d94/ipaserver/plugins/ldap2.py#L202
    diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py
    index 095e74b2a..c46da7c15 100644
    --- a/ipaserver/plugins/ldap2.py
    +++ b/ipaserver/plugins/ldap2.py
    @@ -199,6 +199,18 @@ class ldap2(CrudBackend, LDAPCache):
                 else:
                     os.environ['KRB5CCNAME'] = ccache
     
    +            import time
    +            import gssapi
    +            time.sleep(25)
    +            from pdb_clone import pdb
    +            pdb.set_trace_remote()
    +
    +            store = {'ccache': ccache}
    +            creds = gssapi.Credentials(
    +                usage="initiate", name=None, store=store
    +            )
    +            creds.lifetime
    +
                 principal = krb_utils.get_principal(ccache_name=ccache)
     
                 client.gssapi_bind(server_controls=serverctrls,
    1. trigger plugin, for example, ipa dnszone-find ipa.test.

    I modified (in FreeIPA PR) krb_utils.get_principal and check it for that PR. I expect that it will raise ExpiredCredentialsError after expiration, but nothing happens.

    Next (in the same method) gssapi_bind to LDAP unexpectedly (for me) works.
    Something like this:

    auth_tokens = ldap.sasl.sasl({}, 'GSS-SPNEGO')
    conn = ldap.initialize(
        "ldapi://%2Frun%2Fslapd-IPA-TEST.socket",
    )
    conn.sasl_interactive_bind_s('', auth_tokens, None, None)

    It seems that gssproxy on every gss_acquire_cred(may be a145ea3 and gpp_store_remote_creds) attempts to refresh expired ccache, but overwrite with wrong(?) credentials ( different default principal (even the original one differs)) based on client_keytab:/var/lib/ipa/gssproxy/http.keytab:

    [root@master1 /]# ls -lat /run/ipa/ccaches/ | head -n 3
    total 380
    drwsrws---+ 2 ipaapi ipaapi 4096 Jun  8 15:27 .
    -rw-rw----  1 apache ipaapi 2906 Jun  8 15:27 admin@IPA.TEST-n5hAtw
    [root@master1 /]# KRB5_KTNAME=/var/lib/ipa/gssproxy/http.keytab /usr/sbin/gssproxy -d --extract-ccache /run/ipa/ccaches/admin@IPA.TEST-n5hAtw --into-ccache ~/decryptedccache
    [2021/06/08 15:27:43]: Debug Enabled (level: 1)
    [2021/06/08 15:27:43]: Service: Extract Ccache, Keytab: /var/lib/ipa/gssproxy/http.keytab, Enctype: 18
    decrypted
    [root@master1 /]# klist ~/decryptedccache
    Ticket cache: FILE:/root/decryptedccache
    Default principal: admin@IPA.TEST
    
    Valid starting       Expires              Service principal
    06/08/2021 15:27:30  06/08/2021 15:27:50  krbtgt/IPA.TEST@IPA.TEST
            for client HTTP/master1.ipa.test@IPA.TEST, renew until 06/09/2021 15:27:30
    06/08/2021 15:27:30  06/08/2021 15:27:49  HTTP/master1.ipa.test@IPA.TEST
            renew until 06/09/2021 15:27:29
    

    after expiration and gss_acquire_cred:

    [root@master1 /]# ls -lat /run/ipa/ccaches/ | head -n 3
    total 380
    drwsrws---+ 2 ipaapi ipaapi 4096 Jun  8 15:29 .
    -rw-------  1 ipaapi ipaapi 2104 Jun  8 15:29 admin@IPA.TEST-n5hAtw
    [root@master1 /]# KRB5_KTNAME=/var/lib/ipa/gssproxy/http.keytab /usr/sbin/gssproxy -d --extract-ccache /run/ipa/ccaches/admin@IPA.TEST-n5hAtw --into-ccache ~/decryptedccache_
    [2021/06/08 15:29:47]: Debug Enabled (level: 1)
    [2021/06/08 15:29:47]: Service: Extract Ccache, Keytab: /var/lib/ipa/gssproxy/http.keytab, Enctype: 18
    decrypted
    [root@master1 /]# klist ~/decryptedccache_
    Ticket cache: FILE:/root/decryptedccache_
    Default principal: HTTP/master1.ipa.test@IPA.TEST
    
    Valid starting       Expires              Service principal
    06/08/2021 15:29:32  06/08/2021 15:29:52  krbtgt/IPA.TEST@IPA.TEST
            renew until 06/09/2021 15:29:32
    
  5. simo5 commented on Jun 8, 2021

    @simo5
    Member

    I do not see why krb_utils.get_princpial would raise any exception, the information is available, then fact a ccache is expired does not invalidate it's contents.

    Have you checked what's in creds.lifetime?
    I recall that we did change acquire_cred to cause the mechglue to explicity inquire the credentials if lifetime is requested during a gss_acquire_cred() call. So I am sureprised that gssapi.Credentials() wouldn't cause that call to happen (I do not see it in the log).

    As for the behavior on initiate:
    A client_keytab should not be specified if you do not intend to have gss-proxy use it for initiation.
    Alternatively you must insure the caller always provide the name for the credentials to use, so that gss-proxy will not select a client keytab with an unmatching name in it.

  6. simo5 commented on Jun 8, 2021

    @simo5
    Member

    Ok, so setting name=None if you mean to test "admin" credentials is definitely not ok given's gssproxy configuration for HTTP, so the log you gave me is not useful, as I see that what happens is that a new credential based on client_keytab is returned.

  7. stanislavlevin commented on Jun 8, 2021

    @stanislavlevin
    ContributorAuthor

    I do not see why krb_utils.get_princpial would raise any exception, the information is available, then fact a ccache is expired does not invalidate it's contents.

    Based on the current behaviour of python-gssapi for non-gssproxy env I expect ExpiredCredentialsError. For example,

    [root@master1 /]# cat test.py
    import time
    
    import gssapi
    
    store = {'ccache': '/tmp/admin_ccache'}
    creds = gssapi.Credentials(usage='initiate', name=None, store=store)
    for i in range(1, 12):
        print(creds.name)
        time.sleep(2)
    
    [root@master1 /]# echo Secret123 | KRB5CCNAME=/tmp/admin_ccache kinit
    [root@master1 /]# python3 test.py
    admin@IPA.TEST
    admin@IPA.TEST
    admin@IPA.TEST
    admin@IPA.TEST
    admin@IPA.TEST
    admin@IPA.TEST
    admin@IPA.TEST
    admin@IPA.TEST
    admin@IPA.TEST
    Traceback (most recent call last):
      File "//test.py", line 8, in <module>
        print(creds.name)
      File "/usr/lib64/python3.9/site-packages/gssapi/creds.py", line 72, in name
        return self.inquire(name=True, lifetime=False,
      File "/usr/lib64/python3.9/site-packages/gssapi/creds.py", line 259, in inquire
        res = rcreds.inquire_cred(self, name, lifetime, usage, mechs)
      File "gssapi/raw/creds.pyx", line 357, in gssapi.raw.creds.inquire_cred
    gssapi.raw.exceptions.ExpiredCredentialsError: Major (720896): The referenced credential has expired, Minor (100001): Success
    

    So, I can't know the principal name from expired creds using python-gssapi.

    Have you checked what's in creds.lifetime?
    I recall that we did change acquire_cred to cause the mechglue to explicity inquire the credentials if lifetime is requested during a gss_acquire_cred() call. So I am sureprised that gssapi.Credentials() wouldn't cause that call to happen (I do not see it in the log).

    creds.lifetime is always 20.

    As for the behavior on initiate:
    A client_keytab should not be specified if you do not intend to have gss-proxy use it for initiation.
    Alternatively you must insure the caller always provide the name for the credentials to use, so that gss-proxy will not select a client keytab with an unmatching name in it.

    Yes, this is the actual behaviour I see.
    I provide the default settings of gssproxy for IPA, where the service/ipa-api is in use, nothing was changed there.

  8. simo5 commented on Jun 8, 2021

    @simo5
    Member

    The problem is that you are calling this:
    creds = gssapi.Credentials(usage='initiate', name=None, store=store)
    Where name=None, and gss-proxy in this case is allowed to obtain a new fresh credential for you with the keytab in the configuration.
    If you want to test for expired cred you need to pass the name of principal to get creds for so that gss-proxy is not going to try the keytab

  9. stanislavlevin commented on Jun 8, 2021

    @stanislavlevin
    ContributorAuthor

    that example is for non-gssproxy environment.

  10. stanislavlevin commented on Jun 8, 2021

    @stanislavlevin
    ContributorAuthor

    The problem is that you are calling this:
    creds = gssapi.Credentials(usage='initiate', name=None, store=store)
    Where name=None, and gss-proxy in this case is allowed to obtain a new fresh credential for you with the keytab in the configuration.
    If you want to test for expired cred you need to pass the name of principal to get creds for so that gss-proxy is not going to try the keytab

    I see that.

    I'm just trying to demonstrate you the issue.
    In this example #33 (comment)
    creds = gssapi.Credentials(usage='initiate', name=None, store=store) was a visual replacement of ipalib/krb_utils::get_credentials, which is always called with name=None.

    Regarding lifetime I tried to set name to admin@IPA.TEST.
    code:

                principal = "admin@IPA.TEST"
                name = gssapi.Name(principal, gssapi.NameType.kerberos_principal)
                store = {'ccache': ccache}
                creds = gssapi.Credentials(
                    usage="initiate", name=name, store=store
                )
                creds.lifetime
    
    

    result for valid (not expired) creds is some constant value (not changed from call to call for credentials instance):

    (Pdb) creds.lifetime
    16
    

    log:
    gssproxy.log

    Instantiation of Credentials on expired creds:

    (Pdb) creds = gssapi.Credentials(usage="initiate", name=name, store=store)
    *** gssapi.raw.exceptions.MissingCredentialsError: Major (458752): No credentials were supplied, or the credentials were unavailable or inaccessible, Minor (2598845123): No credentials cache found
    
  11. simo5 commented on Jun 8, 2021

    @simo5
    Member

    Ok, so the second one is kind of expected, given gss-proxy cannot get you creds for admin and the original creds are expired.
    But the creds.lifetime is not adjusted ...
    I need to find some time to reproduce in this scenario, thanks for the examples.

    @frozencemetery do you know if we have a test setup somewhere that I can use to try this out ?

  12. frozencemetery commented on Jun 8, 2021

    @frozencemetery
    Member

    Hi, not really sure what you're asking for. Long-lived machines isn't a thing we can do on our infrastructure. If you need a KDC set up, you could use gssapi-console, but it's only a handful of commands to set one up (or fewer, if you use IPA).

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions