ACCESS CONTROL LISTS FOR CVS (Proposal) ======================================== This document discusses the design and implementation of ACL (Access Control Lists) for the cvs-nserver. This document is open for comments, suggestions and modifications. You can commit new revisions of it into CVS after discussion in cvs-nserver-devel mailing list (available on http://sf.net/projects/cvs-nserver/). $Id: acl-rfc.txt,v 1.1.2.7 2002/03/31 18:22:13 tyranny Exp $ Primary Authorization Features =============================== The concept of "no ACL" is used a lot, and this could sometimes lead to (unexpected) opening of repository to extemely wide access. In short, when the ACL file is non-existent or becomes empty after revoking all privileges granted, the "default ACLs" come into play. Default ACLs simply allow everything. Maybe we need some flag file in the repository that means "default ACLs is deny everyone". We need the following basic authorization features: - the ACLs are assigned on per-file-on-branch, not on per-file basis. Same goes for directories case. Rationale: a very common task is "let support engineers commit patches to the stable branch, and not to the trunk, which is for main developers only"; NB: In CVS you cannot do anything with a "file" (or directory) per se. Even when you delete a file, you delete it only on a specific branch. Although, file-level ACLs could be emulated with default directory ACLs, see below. This should be considered from the very first days of development. Maybe it would not be implemented right from the start, but switching from "acl-per-file" to "acl-per-branch" would seem like hell to do. - if there are no ACLs on a certain branch, then ACLs on a trunk are used instead. If there are no ACLs on a trunk, then default directory ACLs for certain branch are used. As a last resort, the default ACLs are used. This could help to emulate the "per-file" ACLs. Rationale: often the developers working on a trunk and developers fixing bugs on a stable branch are the same people, and there is no need to setup different ACLs. NB: From now on when speaking about the "file", we mean "file on a branch", if it's not explicitly specified otherwise. Same for a directory. - of course, user groups are fully supported. In general, when we are thinking about the "user", we mean "user or group of users", if it's not explicitly specified otherwise. - the basic operations that should be controlled for each branch in a project or for a file (that is, file on a branch) are: - check revision out (read access); Rationale: don't let somebody see what happens on the development branch. - check revision in (write access); Rationale: obviously, don't let somebody commit anything on this branch; - the basic operations that should be controlled for each directory are: - accessing the files and subdirectories in this directory; - modifying (checking in, adding and removing files, creating directories); Rationale: obviously, we need to handle two simple and frequent cases: "give somebody read-only access to the repository", "don't let somebody access any files inside of a directory (with subdirectories)". Rationale: indeed, the 99% of files in each given directory have the same permissions. But, sometimes we need to single out one important file and harden its security. - default subdirectory ACLs. If the directory does not have explicitly specified ACLs, they are taken from its parent directory. Rationale: see the convenience of "BSD groups". - when checking the access rights, the directories are traversed from the top directory to the directory where the file resides; Rationale: obvious to anyone who has ever worked with filesystem permissions :) Also, this brings sane implementation of "default subdirectory ACLs". Also, it greatly simplifies the recursive processing of repositories. - the basic operations that should be controlled for each module are: - tag (non-branch) revisions (this includes moving and deleting tags); - create branches; Rationale: let the Project Release Manager do her job, don't let mere developers to mark releases. It doesn't seem worth the trouble to make "tagging" access control be on a per-file basis, because the keyword in "Project Release Manager" is "Project". - list of ACL administrators for this module, who can setup the permissions as they see fit. Rationale: why not per-directory administrators? If there are say two distinct development group working on the same project, and they need one administrator in each group, why not let them have separate administrator for ./project/subsystem1 and ./project/subsystem2 directories? Answer: it seems better to create two modules: subsystem1 and subsystem2, and then to create an ampersand-module called 'project' that merges those two modules. Rationale: if administrators can setup only ACLs, and not to modify files in the repository, the security risks are greatly reduced. Surely, the attacks of kind "no one is allowed to checkin any file" could be tolerated "better" than attacks of kind "every file has a thousand junk revisions applied, and all branches are renamed to something funny". For that reason, users who are ACL administrators for this module could not appear in the file-level ACLs (such entries are ignored, and the special script could be written that checks the sanity of permissions in the repository). - I don't think there is a need of concept for "owner" and "owner group" for each file. Rationale: default permissions for files in any given directory completely supersede the notions of owner/group. Also, the administrator should be the only one, so to speak. - there is a notion of "repository administrator", who can modify the list of ACL administrators for each module, and doesn't do anything else (mostly, at least she doesn't have root access to all the files in the repository). This list is probably not complete, though I think I've covered the essential parts. What is needed now: you should think up a real-life case that you think is not covered in this text, and we will think together if that case could be a) implemented by the above; b) needs a whole new feature; c) could be scrapped and forgotten. Basic Semantics ================ The ACLs for a particular directory or a file are generated traversing from the "top-level directory", i.e., repository directory, towards the particular directory or a file. The basic semantics is restrictive-only. You cannot give additional rights to user in a directory, if parent directory prohibits those rights. E.g., if the module directory grants only "access" rights to user, she could not in any way get a "modify" or "checkin" rights in that module. We must strictly differentiate between two cases: - the directory itself does not have an associated Access Control List (i.e., there is no file DIRACL in that directory, or it is empty: this is rather common case); - the directory has an associated Acess Control List. Repository policy ------------------ "Repository policy" is an "allow"/"deny" decision which is used when there were no associated ACLs in all of the directories along the particular path. Repository policy is controlled by the `CVSROOT/default-deny' file: - if this file exists (its contents is ignored), the repository policy is "deny"; - if this file does not exist, the repository policy is "allow". Directory permissions semantics -------------------------------- Here is how the permissions are checked for the particular directory for the particular user: - we set the current effective permissions to 'all'; - for each of the the parent directories down from the top-level directory: - if the parent directory does not have an ACE for user, it is skipped; - if the parent directory has such an ACE, we AND-mask the current effective permissions with the permissions we get for that parent directory; [ E.g., those permissions could still be "all", or become "access", or become completely restrictive, "none". Note that there is no way for the effective permissions to become less restrictive while traversing the list of [parent directories. ] - now, we have four cases: - none of the parent directories did have an ACE for the user; target directory does not have an ACE for the user: we check the requested permissions against the repository policy; - none of the parent directories did have an ACE for the user; target directory does have an ACE for the user: we check the requested permissions against the target directory ACL; - at least one parent directory did mentioned user in an ACE; target directory does not have an ACE for the user: we check the requested permissions agains combined permissions of parent directories; - at least one parent directory did mentioned user in an ACE; target directory does have an ACE for the user: we check the requested permissions agains combined permissions of parent directories combined with permissions of target directory; That's all. What does that all mean? Here is the short break-down of use cases: - user is not mentioned in ACLs alongside the traversing path at all: repository policy comes into effect (that's the only time it is the case); - user is mentioned in one of the parent directories: he will never be permitted to do besides for what was allowed in those directories; - the sequence above covers and fixes the bug found by Alex, where the case of "parent dirs mention user" + "target dir does not mention user" give the "permission denied" to him, instead of "what is specified in parent directory; Quality Assurance ================== All of the ACL-related functionality is fully unit-tested and (hopefully) feature-tested. Always create test-cases for new code in acl/ subdirectory; always create test-cases for new features in cvs binary itself. One cannot overemphasize the importance of this. This has already helped me a lot during the development, and I think it'll be crucial alongside, when the functionality will be extended. Internal Structure =================== Internal structure of on-disk ACL-related files is documented in the acl-on-disk.txt (soon to be written).