gssapi / gssproxy

A proxy for GSSAPI | Docs at https://github.com/gssapi/gssproxy/tree/main/docs
Other
44 stars 28 forks source link

Handle impersonation of oneself #30

Closed mw-a closed 3 years ago

mw-a commented 3 years ago

As discussed on the mailing list[1]:

When trying to impersonate the user which has been selected as impersonation credential, an AD KDC returns error:

GSSX_RES_ACQUIRE_CRED( status: { 851968 { 1 2 840 113554 1 2 2 } 2529638928 "Unspecified GSS failure. Minor code may provide more information" "KDC has no support for padata type" [ ] } output_cred_handle: )

Apparently, to impersonate oneself is not allowed. Also, it is likely not even necessary: If we can get impersonation credentials from credstores, we can at least try to short circuit and get actual user credentials the same way.

With this patch it becomes possible to delegate the acquisition of e.g. cifs mount credentials from cifs.upcall into gssproxy and use the host identity (e.g. HOSTNAME$@REALM of AD) while it is also being selected as impersonation credential due to the order of keys in the keytab.

[1] https://lists.fedorahosted.org/archives/list/gss-proxy@lists.fedorahosted.org/thread/WO3IN7Q3BKQ5VCNDAPIBNFANBJEZ4OSD/.

simo5 commented 3 years ago

Thanks for the PR btw! Very welcome!

mw-a commented 3 years ago

I just had a discussion with a friend about the necessity and danger of the trusted flag for my use-case. I might be abusing it here and creating a problem where none actually exists.

So to recap: What I'm actually trying to do is get the fallback to krb5_principal for the machine identity if none is provided by the client, in this case cifs.upcall upon mount as root[1]. This is guarded by use_service_principal which can only be true if the trusted flag is enabled[2]. Since the service also has impersonate enabled for all other usage by all other uids, this unconditionally sends me down the impersonation codepath.

Perhaps the issue can be avoided by tweaking try_impersonate() for what I'm trying to do?

Looking at the code and documentation again I get the feeling that the trusted flag opens the service up to other use-cases as well, i.e. actual impersonation requests by the client (case ACQ_IMPNAME). Is that correct? Am I creating a security problem with this?

[1] https://github.com/gssapi/gssproxy/blob/main/src/gp_creds.c#L389 [2] https://github.com/gssapi/gssproxy/blob/main/src/gp_creds.c#L354

frozencemetery commented 3 years ago

Apparently, to impersonate oneself is not allowed

It's not just AD that denies this. The problem is articulated in MS-SFU 3.1.5.1.2, which basically says that a self-s4u request generates the same reply that a KDC without s4u support would generate.

So it's really better not to attempt the operation at all.

mw-a commented 3 years ago

I did a packet capture. I see no actual error from the KDC: packet-sequence Here's the TGS-REQ: tgs-req And here's the TGS-REP: tgs-rep Looks to me as if we get exactly what we asked for.

The error seems to actually come from libkrb5 here https://github.com/krb5/krb5/blob/f7b3cb8bbe90817f7bfbc545f1e427c16f52a79c/src/lib/krb5/krb/gc_via_tkt.c#L258. It seems to me, the check assumes that S4U2Self failed if the KDC responds with a client name that equals the service name we asked for in the req-body and not the client name from the PA-FOR-USER field. But in the case that both are identical that assumption is wrong because then the KDC did actually fulfil the S4U2Self request as ordered. It's still a needlessly complex request but technically correct and fulfilled.

frozencemetery commented 3 years ago

It seems to me, the check assumes that S4U2Self failed if the KDC responds with a client name that equals the service name we asked for in the req-body and not the client name from the PA-FOR-USER field. But in the case that both are identical that assumption is wrong because then the KDC did actually fulfil the S4U2Self request as ordered. It's still a needlessly complex request but technically correct and fulfilled.

This is working as intended - see my comment on MS-SFU above :)

simo5 commented 3 years ago

Looking at the code and documentation again I get the feeling that the trusted flag opens the service up to other use-cases as well, i.e. actual impersonation requests by the client (case ACQ_IMPNAME). Is that correct? Am I creating a security problem with this?

Would you mind pasting your configuration here?

Usually the trusted flag is used together with a euid flag restricting access to the service to a specific user that you trust. You can also restrict by other means (ie file permissions on the socket) of course, but the concept is the same.

So you really have to trust that user not to try "bad things"(TM).

simo5 commented 3 years ago

It seems to me, the check assumes that S4U2Self failed if the KDC responds with a client name that equals the service name we asked for in the req-body and not the client name from the PA-FOR-USER field. But in the case that both are identical that assumption is wrong because then the KDC did actually fulfil the S4U2Self request as ordered. It's still a needlessly complex request but technically correct and fulfilled.

This is working as intended - see my comment on MS-SFU above :)

It is a matter of semantics. In theory MIT could decide that the response is ok and not return a failure to the calling application (gss-proxy), but in this case MIT decide to consider this a failure because a s4u operation was requested and it was not "technically" fulfilled.

It is merely an implementation choice. That said, this is how MIT behaves so we need to fix gssproxy to take that into account :-)

mw-a commented 3 years ago

Looking at the code and documentation again I get the feeling that the trusted flag opens the service up to other use-cases as well, i.e. actual impersonation requests by the client (case ACQ_IMPNAME). Is that correct? Am I creating a security problem with this?

Would you mind pasting your configuration here?

My config which exercises the code path is (the comments reflect my intent based on my unterstanding of the code):

[service/cifs]
  mechs = krb5
  socket = /var/lib/gssproxy/cifs.sock
  cred_store = keytab:/etc/krb5.keytab
  cred_usage = initiate
# allow process with any euid to use the service and receive impersonated
# tickets for services
  allow_any_uid = yes
# allow euid access to keytab
  trusted = yes
# allow impersonation
  impersonate = yes
# allow process with euid 0 to use the keytab
  euid = 0
# fall back to default principal if no name is given
  krb5_principal = FEDORA33$

Usually the trusted flag is used together with a euid flag restricting access to the service to a specific user that you trust. You can also restrict by other means (ie file permissions on the socket) of course, but the concept is the same. So you really have to trust that user not to try "bad things"(TM).

The idea of the config is to allow any user to receive a personal Kerberos ticket for access to cifs mounts, acquired for them by gssproxy using impersonation (allow_any_uid + impersonate). This works because in conjunction with trusted because it is checked against euid == target_uid in gp_cred.c and usage of the service keytab denied if not.

For the special case of euid 0 I would like it to fallback to krb5_principal so that the initial mount happens with the machine or some kind of non-privileged mount identity, similar to what NFS' gssd does but without the need to extend cifs.upcall to do it itself. This principal might need impersonation by gssproxy to acquire a ticket as well. It could be argued that trusted is indeed required here because this principal will likely not match the name associated with euid 0 (e.g. root). So euid does want to impersonate someone else and needs to be trusted not to abuse that.

What shouldn't happen is that any uid can explicitly request impersonation of anyone else (case ACQ_IMPNAME). Is that a possibility or is it already handled and I just haven't found the check?

Alternatively, it would be an option to disable impersonate for the configured euid so that it doesn't impersonate but just gets a credential using the keytab like any other privilege-separated service that doesn't have direct access to its keytab managed by gssproxy. Is this currently possible in conjuction with allow_any_uid and impersonate and I just haven't found the knob to turn?

mw-a commented 3 years ago

Force-pushed again because I found I was memory-leaking target_name in the case that a name was requested but did not match the acquired impersonation credential (e.g. krb5_principal = cifs-mount, i.e. a non-privileged mount-user which is not the machine identity is configured).

Also, I had updated the comment to reflect that it's MIT krb5 which gives the padata support error, not the AD KDC.

simo5 commented 3 years ago

Looking at the code and documentation again I get the feeling that the trusted flag opens the service up to other use-cases as well, i.e. actual impersonation requests by the client (case ACQ_IMPNAME). Is that correct? Am I creating a security problem with this?

Would you mind pasting your configuration here?

My config which exercises the code path is (the comments reflect my intent based on my unterstanding of the code):

[service/cifs]
  mechs = krb5
  socket = /var/lib/gssproxy/cifs.sock
  cred_store = keytab:/etc/krb5.keytab
  cred_usage = initiate
# allow process with any euid to use the service and receive impersonated
# tickets for services
  allow_any_uid = yes
# allow euid access to keytab
  trusted = yes
# allow impersonation
  impersonate = yes
# allow process with euid 0 to use the keytab
  euid = 0
# fall back to default principal if no name is given
  krb5_principal = FEDORA33$

Usually the trusted flag is used together with a euid flag restricting access to the service to a specific user that you trust. You can also restrict by other means (ie file permissions on the socket) of course, but the concept is the same. So you really have to trust that user not to try "bad things"(TM).

The idea of the config is to allow any user to receive a personal Kerberos ticket for access to cifs mounts, acquired for them by gssproxy using impersonation (allow_any_uid + impersonate). This works because in conjunction with trusted because it is checked against euid == target_uid in gp_cred.c and usage of the service keytab denied if not.

Ok, your configuration does that.

For the special case of euid 0 I would like it to fallback to krb5_principal so that the initial mount happens with the machine or some kind of non-privileged mount identity, similar to what NFS' gssd does but without the need to extend cifs.upcall to do it itself. This principal might need impersonation by gssproxy to acquire a ticket as well. It could be argued that trusted is indeed required here because this principal will likely not match the name associated with euid 0 (e.g. root). So euid does want to impersonate someone else and needs to be trusted not to abuse that.

Correct.

What shouldn't happen is that any uid can explicitly request impersonation of anyone else (case ACQ_IMPNAME). Is that a possibility or is it already handled and I just haven't found the check?

In case of a user_requested is true (which is if target_uid != euid), then we forcibly overwrite requested_name with the name of the user obtained via uid_to_name(), so non-trusted users cannot impersonate anyone but themselves.

Alternatively, it would be an option to disable impersonate for the configured euid so that it doesn't impersonate but just gets a credential using the keytab like any other privilege-separated service that doesn't have direct access to its keytab managed by gssproxy. Is this currently possible in conjuction with allow_any_uid and impersonate and I just haven't found the knob to turn?

I think your current patch is the simplest way to go about it, and I now verified your configuration is a legitimate case, so let's fix the last issue and merge it in.

mw-a commented 3 years ago

Thanks for your explanations. So everything should be fine and ready to go.

simo5 commented 3 years ago

Thanks a lot!

simo5 commented 3 years ago

@frozencemetery I think we may want to backport this to Fedora/RHEL, WDYT?

frozencemetery commented 3 years ago

Rolled it into gssproxy-0.8.4 and built that for rawhide. Willing to backport elsewhere if it would be helpful, of course; just file a bugzilla for it.