kanidm/designs/access_profiles_and_security.rst
Pando85 4dcad60374 Extend testing of default entries
Add default entries test to apply behaviours according to
`designs/default_idm_layout.rst`.

Add expected behaviours for:
  - Users
  - Account managers
  - Group managers
  - Admins
  - People Managers
  - Anonymous clients
  - Radius servers

Also, refactor `kanidmd_client` tests to separate into different files
and fix some documentation typos

Resolves: #108
2020-05-08 10:27:39 +10:00

437 lines
18 KiB
ReStructuredText

Access Profiles
---------------
Access profiles are a way of expressing which persons are allowed what actions to be
performed on any database record (object) in the system.
As a result, there are specific requirements to what these can control and how they are
expressed.
Access profiles define an action of allow or deny: Denies are enforced before allows, and
will override even if applicable. They should only be created by system access profiles,
because we have certain requirements to deny certain changes.
Access profiles are stored as entries and are dynamically loaded into a structure that is
more efficent for use at runtime. Schema and it's transactions are a similar implementation.
Search Requirements
-------------------
A search access profile, must be able to limit the content of a search request and it's
scoping.
A search access profile, must be able to limit the returned set of data from the objects
visible.
An example is that user Alice should only be able to search for objects where the class
is person, and where they are a memberOf "visible" group. Alice should only be able to
see those users displayNames (not their legalName for example), and their public email.
Worded a bit differently. You need permission over the scope of entries, you need to be able
to read the attribute to filter on it, and you need to be able to read the attribute to recieve
it in the result entry.
Threat: If we search for '(&(name=william)(secretdata=x))', we should not allow this to
proceed because you don't have the rights to read secret data, so you should not be allowed
to filter on it. How does this work with two overlapping ACPs? For example one that allows read
of name and description to class = group, and one that allows name to user. We don't want to
say '(&(name=x)(description=foo))' and have it allowed, because we don't know the target class
of the filter. Do we "unmatch" all users because they have no access to the filter components? (Could
be done by inverting and putting in an AndNot of the non-matchable overlaps). Or do we just
filter our description from the users returned (But that implies they DID match, which is a disclosure).
More concrete:
search {
action: allow
targetscope: Eq("class", "group")
targetattr: name
targetattr: description
}
search {
action: allow
targetscope: Eq("class", "user")
targetattr: name
}
SearchRequest {
...
filter: And: {
Pres("name"),
Pres("description"),
}
}
A potential defense is:
acp class group: Pres(name) and Pres(desc) both in target attr, allow
acp class user: Pres(name) allow, Pres(desc) deny. Invert and Append
So the filter now is:
And: {
AndNot: {
Eq("class", "user")
},
And: {
Pres("name"),
Pres("description"),
},
}
This would now only allow access to the name/desc of group.
If we extend this to a third, this would work. But a more complex example:
search {
action: allow
targetscope: Eq("class", "group")
targetattr: name
targetattr: description
}
search {
action: allow
targetscope: Eq("class", "user")
targetattr: name
}
search {
action: allow
targetscope: And(Eq("class", "user"), Eq("name", "william"))
targetattr: description
}
Now we have a single user where we can read desc. So the compiled filter above as:
And: {
AndNot: {
Eq("class", "user")
},
And: {
Pres("name"),
Pres("description"),
},
}
This would now be invalid, first, because we would see that class=user and william has no name
so that would be excluded also. We also may not even have "class=user" in the second ACP, so we can't
use subset filter matching to merge the two.
As a result, I think the only possible valid solution is to perform the initial filter, then determine
on the candidates if we *could* have have valid access to filter on all required attributes. IE
this means even with an index look up, we still are required to perform some filter application
on the candidates.
I think this will mean on a possible candidate, we have to apply all ACP, then create a union of
the resulting targetattrs, and then compared that set into the set of attributes in the filter.
This will be slow on large candidate sets (potentially), but could be sped up with parallelism, caching
or other. However, in the same step, we can also apply the step of extracting only the allowed
read target attrs, so this is a valuable exercise.
Delete Requirements
-------------------
A delete profile must contain the content and scope of a delete.
An example is that user Alice should only be able to delete objects where the memberOf is
"purgeable", and where they are not marked as "protected".
Create Requirements
-------------------
A create profile defines a filtering limit on what content can be created and it's requirements.
A create profile defines a limit on what attributes can be created in addition to the filtering
requirements.
An example is user Alice should only be able to create objects where the class is group, and can
only name the group - they can not add members to the group.
A content requirement could be something such as the value an attribute can contain must conform to a
regex, IE, you can create a group of any name, except where the name contains "admin" somewhere
in it's name. Arguable, this is partially possible with filtering.
For example, we want to be able to limit the classes that someone *could* create on something
because classes often are used as a security type.
Modify Requirements
-------------------
A modify profile defines a filter limit of what can be modified in the directory.
A modify profile defines a limit of what attributes can be altered in the modification.
A modify profile defines a limit on the modlist actions: For example you may only be allowed to
ensure presence of a value. (Modify allowing purge, not-present, and presence).
Content requirements (see create requirements) are out of scope at the moment.
An example is Alice should only be able to modify a users password if that user is a member of the
students group.
Note, modify, does not imply *read* of the attribute. Care should be taken that we don't disclose
the current value in any error messages if the operation fails.
Targetting Requirements
-----------------------
The target of an access profile should be a filter defining the objects that this applies to.
THe filter limit for the profiles of what they are acting on requires a single special operation
which is the concept of "targetting self". For example, we could define a rule that says "members
of group X are allowed self-write mobile phone number".
An extension to the filter code, could allow an extra filter enum of "Self", that would allow this
to operate correctly, and would consume the entry in the event as the target of "Self". This would
be best implemented as a compilation of self -> eq(uuid, self.uuid).
Implementation Details
----------------------
CHANGE: Receiver should be a group, and should be single value/multivalue? Can *only* be a group.
Example profiles:
search {
action: allow
receiver: Eq("memberof", "admins")
targetscope: Pres("class")
targetattr: legalName
targetattr: displayName
description: Allow admins to read all users names
}
search {
action: allow
receiver: Self
targetscope: Self
targetattr: homeAddress
description: Allow everyone to read only their own homeAddress
}
delete {
action: allow
receiver: Or(Eq("memberof", "admins), Eq("memberof", "servicedesk"))
targetscope: Eq("memberof", "tempaccount")
description: Allow admins or servicedesk to delete any member of "temp accounts".
}
// This difference in targetscope behaviour could be justification to change the keyword here
// to prevent confusion.
create {
action: allow
receiver: Eq("name", "alice")
targetscope: And(Eq("class", "person"), Eq("location", "AU"))
createattr: location
createattr: legalName
createattr: mail
createclass: person
createclass: object
description: Allow alice to make new persons, only with class person+object, and only set
the attributes mail, location and legalName. The created object must conform to targetscope
}
modify {
action: allow
receiver: Eq("name", "claire")
targetscope: And(Eq("class", "group"), Eq("name", "admins"))
presentattr: member
description: Allow claire to promote people as members of the admins group.
}
modify {
action: allow
receiver: Eq("name", "claire")
targetscope: And(Eq("class", "person"), Eq("memberof", "students"))
presentattr: sshkeys
presentattr: class
targetclass: unixuser
description: Allow claire to modify persons in the students group, and to grant them the
class of unixuser (only this class can be granted!). Subsequently, she may then give
the sshkeys values as a modification.
}
modify {
action: allow
receiver: Eq("name", "alice")
targetscope: Eq("memberof", "students")
removedattr: sshkeys
description: Allow allice to purge or remove sshkeys from members of the students group,
but not add new ones
}
modify {
action: allow
receiver: Eq("name", "alice")
targetscope: Eq("memberof", "students")
removedattr: sshkeys
presentattr: sshkeys
description: Allow alice full control over the ssh keys attribute on members of students.
}
// This may not be valid: Perhaps if <*>attr: is on modify/create, then targetclass, must
// must be set, else class is considered empty.
//
// This profile could in fact be an invalid example, because presentattr: class, but not
// targetclass, so nothing could be granted.
modify {
action: allow
receiver: Eq("name", "alice")
targetscope: Eq("memberof", "students")
presentattr: class
description: Allow alice to grant any class to members of students.
}
Formalised Schema
-----------------
A complete schema would be:
attributes:
* acp_allow single value, bool
* acp_enable single value, bool
* acp_receiver single value, filter
* acp_targetscope single value, filter
* acp_search_attr multi value, utf8 case insense
* acp_create_class multi value, utf8 case insense
* acp_create_attr multi value, utf8 case insense
* acp_modify_removedattr multi value, utf8 case insense
* acp_modify_presentattr multi value, utf8 case insense
* acp_modify_class multi value, utf8 case insense
classes:
* access_control_profile MUST [acp_receiver, acp_targetscope] MAY [description] MAY acp_allow
* access_control_search MUST [acp_search_attr]
* access_control_delete
* access_control_modify MAY [acp_modify_removedattr, acp_modify_presentattr, acp_modify_class]
* access_control_create MAY [acp_create_class, acp_create_attr]
Important, but empty sets really mean empty sets! The ACP code will assert that both
access_control_profile *and* one of the search/delete/modify/create classes exists on an ACP. An
important factor of this design is now the ability to *compose* mulitple ACP's to a single entry
allowing a create/delete/modify to exist! However, each one must still list their respective actions
to allow proper granularity.
Search Application
------------------
The set of access controls is checked, and the set where receiver matches the current identified
user is collected. These then are added to the users requested search as:
And(<User Search Request>, Or(<Set of Search Profile Filters))
In this manner, the search security is easily applied, as if the targets to conform to one of the
required search profile filters, the outer And condition is nullified and no results returned.
Once complete, in the translation of the entry -> proto_entry, each access control and it's allowed
set of attrs has to be checked to determine what of that entry can be displayed. Consider there are
three entries, A, B, C. An ACI that allows read of "name" on A, B exists, and a read of "mail" on
B, C. The correct behaviour is then:
A: name
B: name, mail
C: mail
So this means that the entry -> proto entry part is likely the most expensive part of the access
control operation, but also one of the most important. It may be possible to compile to some kind
of faster method, but initially a simple version is needed.
Delete Application
------------------
Delete is similar to search, however there is the risk that the user may say something like:
Pres("class").
Now, were we to approach this like search, this would then have "every thing the identified user
is allowed to delete, is deleted". A consideration here is that Pres("class") would delete "all"
objects in the directory, but with the access control present, it would limit the delete to the
set of allowed deletes.
In a sense, this is a correct behaviour - they were allowed to delete everything they asked to
delete. However, in another it's not valid: the request was broad and they were not allowed access
to delete everything they request.
The possible abuse here is that you could then use deletes to determine existance of entries in
the database that you do not have access to. This however, requires someone to HAVE a delete
privilege which is itself, very high level of access, so this risk may be minimal.
So the choices are:
* Treat it like search and allow the user to delete "what they are allowed to delete"
* Deny the request, because their delete was too broad, and they should specify better
what they want to delet.
Option 2 seems more correct because the delete request is an explicit request, not a request where
you want partial results - imagine someone wants to delete users A, B at the same time, but only
have access to A. They wwant this request to fail so they KNOW B was not deleted, rather than
succeed and have B still exist with a partial delete status.
However, the issue is Option 2 means that you could have And(Eq(attr, accessible), Eq(attr, denied)), and denial of that, would indicate presence of the denied attr. So option 1 makes sense in terms
of preventing a security risk of info disclosure.
This is also a concern for modification, where the modification attempt may or may not
fail depending on the entries and if you can/can't see them.
BETTER IDEA. You can only delete/modify within the scope of the read you have. If you can't
read it (based on the read rules of search), you can't delete it. This is in addition to the filter
rules of the delete applying as well. So doing a delete of Pres(class), will only delete
in your READ SCOPE and will never disclose if you have no access.
Create Application
------------------
Create seems like the easiest to apply. Ensure that only the attributes in createattr are in the
createevent, ensure the classes only contain the set in createclass, then finally apply
filter_no_index to the entry to entry. If all of this passes, the create is allowed.
A key point, is that there is no union of create aci's - the WHOLE aci must pass, not parts of
multiple. This means if a control say "allows creating group with member" and "allows creating
user with name", creating a gorup with name is not allowed - despite your ability to create
an entry with "name" it's classes don't match. This way, the admin of the service can define
create controls with really specific intent to how they'll be used, without risk of two
controls causing un-intended effects (users that are also groups, or allowing values that
were not intended).
An important consideration is how to handle overlapping aci. If two aci *could* match the create
should we enforce both conditions are upheld? Or only a single upheld aci allows the create?
In some cases it may not be possible to satisfy both, and that would block creates. The intent
of the access profile is that "something like this CAN" be created, so I believe that provided
only a single control passes, the create should be allowed.
Modify Application
------------------
Modify is similar to above, however, we specifically filter on the modlist action of present,
removed or purged with the action. Otherwise, the rules of create stand where provided all requirements
of the modify are "upheld", then it is allowed provided at least a single profile allows the change.
A key difference is that if the modify lists multiple presentattr types, the modify so long as it has
one presentattr of the profile, it is conforming. IE we say "presentattr: name, email", but we
only attempt to modify "email".
Considerations
--------------
* When should access controls be applied? During an operation, we only schema validate after
pre plugins, so likely it has to be "at that point", to ensure schema validity of the entries
we want to assert changes to.
* Self filter keyword should compile to eq("uuid", "...."). When do we do this and how?
* memberof could take name or uuid, we need to be able to resolve this correctly, but this is likely
a memberof issue we need to address, ie memberofuuid vs memberof attr.
* Content controls in create and modify will be important to get right to avoid the security issues
of ldap access controls. Given that class has special importance, it's only right to give it extra
consideration in these controls.
* In the future when recyclebin is added, a re-animation access profile should be created allowing
revival of entries given certain conditions of the entry we are attempting to revive.