diff options
author | Lars Wirzenius <liw@liw.fi> | 2017-07-16 14:31:02 +0300 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2017-07-16 14:31:02 +0300 |
commit | ec8822d3084838427abbdfcaddb6037c78ccec02 (patch) | |
tree | 0cda33834eb55ea99210a2b186b231c618d9a824 | |
parent | 34601e01463c913c19420dfb4bd56ce0804961fe (diff) | |
download | git.liw.fi-ruleset-tests-ec8822d3084838427abbdfcaddb6037c78ccec02.tar.gz |
Add: yarns for gitano ruleset
-rw-r--r-- | yarns.mdwn | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/yarns.mdwn b/yarns.mdwn new file mode 100644 index 0000000..9deb11e --- /dev/null +++ b/yarns.mdwn @@ -0,0 +1,432 @@ +--- +title: "A Gitano ruleset: tests" +author: Lars Wirzenius (liw@liw.fi) +date: 2017-02-05 +... + +Introduction +============================================================================= + +[Gitano][gitano] is the server software I use for my [personal git server][gitliwfi], +and also at [work][ql]. One of Gitano's primary features is a strong +language for defining access control, called [Lace][lace]. However, with +great power comes long shoelaces that get easily loosened. This +document explains what my ruleset does, and why. It is also a test +suite for the ruleset, to make sure it does what it's supposed to, but +avoids some of the more obvious pitfalls. + +[Gitano]: https://www.gitano.org.uk/ +[Lace]: https://www.gitano.org.uk/lace/ +[gitliwfi]: http://git.liw.fi/ +[ql]: http://qvarnlabs.com/ + +Discussion +----------------------------------------------------------------------------- + +* There's going to be repositories that are public to the world (in a + read-only manner) and those that are meant to be private within a + group of people (e.g., all of company staff, or just operations + staff). + +* Some people will have full write access to some repositories, + whereas everyone else (even those with Gitano accounts) have only + read access to those respositories. + +* When outsiders have write access, it may need to be restricted, such + that they can do things, but can't change master or other branches + themselves, or create release tags. We call these guests. + +* There's automated system, such as CI, which only ever need read-only + access. (If we find a need for a bot with write access later, we'll + re-think then. YAGNI.) + +Rough outline for ruleset +----------------------------------------------------------------------------- + +* Only a Gitano admin can create users, groups, and repositories, or + add users to groups. + +* All repository access is controlled via group membership. Each + repository is assumed to have three group associated with it: + `writers`, `readers`, and `guests`. These are per-repository + configuration variables, whose value should be the name of the + Gitano group that defines who are writers, readers, or guests, + correspondingly. + + For example, if `qvarn.git` has `writers` defined as + `qvarnlabs-staff`, then only those in the `qvarnlabs-staff` group + have full write access. + +* All groups have full read access. (Git doesn't really allow limiting + read at a more granular scale than a repository.) + +* Writers have full write access. They can push changes, and update + any branches and tags. + +* Guests are like writers, but can only update branches and tags + prefixed by their Gitano username. + +* Public repositories are marked by setting the configuration variable + `public` to `yes`. Anyone can clone public repositories, pull from + them, and cgit shows them to anyone. Public repositories are + published via the anonymous git prototocol. + + +The ruleset change to standard Gitano ruleset +============================================================================= + +The following lines added to `rules/project.lace` in the +`gitano-admin.git` repository implement the ruleset change, on top of +the standard Gitano ruleset. + + define repo_is_public config/public exact yes + allow "Everyone can read a public repo" op_read repo_is_public + + define user_is_repo_reader group exact ${config/readers} + allow "Readers may read" op_read user_is_repo_reader + + define user_is_repo_writer group exact ${config/writers} + allow "Writers may read and write" op_is_basic user_is_repo_writer + allow "Writers may update any branch" op_is_normal user_is_repo_writer + + define user_is_repo_guest group exact ${config/guests} + define ref_is_for_user ref prefix ${user}/ + allow "Guests may read and write" op_is_basic user_is_repo_guest + allow "Guests may update their own refs" op_is_normal user_is_repo_guest + + +Use cases as automated test scenarios +============================================================================= + +These [yarn][yarn] scenarios need to be run as a Gitano user who is in the +`gitano-admins` group so that they can create users, and do other +priviledged operations. Further, this should be done against a fresh +Gitano, without the test users etc. + +[yarn]: http://liw.fi/cmdtest/ + +Personas for use cases +----------------------------------------------------------------------------- + +* **Ian Inhouse Developer** is a staff member and works on a free software + project with the random silly name Qvarn. + +* **Olive Opshead** is a sysadmin and needs to maintain ops related + repositories, some of which are sensitive. + +* **Steven Sect** is the company secretary and needs to access some + private respositories with internal company data. + +* **Gabriella Guest** is an outside developer, collaborating on Qvarn + with Ian. Gabriella has been granted restricted commit access to the + Qvarn repository. + +* **CI** is an automated system that monitors some repositories to + build, test, and deliver software. + +Ian makes a bug fix in Qvarn +----------------------------------------------------------------------------- + + SCENARIO Ian can make a bug fix in Qvarn + WHEN admin creates user ian + AND admin creates group qvarndevs + AND admin adds ian to qvarndevs + AND admin creates repository qvarn + AND admin sets qvarn config writers to qvarndevs + THEN ian can clone qvarn + WHEN ian creates qvarn branch bugfix + AND ian changes qvarn branch bugfix + THEN ian can push qvarn + WHEN ian merges qvarn branch bugfix to master + THEN ian can push qvarn + FINALLY admin removes things that were created + +Steven can see Qvarn, but not push changes +----------------------------------------------------------------------------- + + SCENARIO Steven can clone Qvarn but not push changes + WHEN admin creates user steven + AND admin creates group qvarn-readers + AND admin adds steven to qvarn-readers + AND admin creates repository qvarn + AND admin sets qvarn config readers to qvarn-readers + AND admin sets qvarn config writers to qvarn-writers + THEN steven can clone qvarn + WHEN steven creates qvarn branch bugfix + AND steven changes qvarn branch bugfix + AND steven merges qvarn branch bugfix to master + THEN steven cannot push qvarn + FINALLY admin removes things that were created + +Ian can make a Qvarn release +----------------------------------------------------------------------------- + + SCENARIO Ian can make a Qvarn release + WHEN admin creates user ian + AND admin creates group qvarndevs + AND admin adds ian to qvarndevs + AND admin creates repository qvarn + AND admin sets qvarn config writers to qvarndevs + THEN ian can clone qvarn + WHEN ian creates qvarn branch bugfix + AND ian changes qvarn branch bugfix + THEN ian can push qvarn + WHEN ian merges qvarn branch bugfix to master + AND ian tags qvarn master branch with qvarn-1.0 + THEN ian can push qvarn with tags + FINALLY admin removes things that were created + +Steven can't push a release tag +----------------------------------------------------------------------------- + + SCENARIO Steven can't tag a Qvarn release + WHEN admin creates user steven + AND admin creates group qvarn-readers + AND admin creates group qvarn-writers + AND admin adds steven to qvarn-readers + AND admin creates repository qvarn + AND admin sets qvarn config readers to qvarn-readers + AND admin sets qvarn config writers to qvarn-writers + THEN steven can clone qvarn + WHEN steven tags qvarn master branch with qvarn-2.0 + THEN steven cannot push qvarn with tags + FINALLY admin removes things that were created + +Gabriella can push changes and tag with her own prefix +----------------------------------------------------------------------------- + + SCENARIO Gabriella can change and tag her own branch + WHEN admin creates user gabriella + AND admin creates group qvarn-guests + AND admin adds gabriella to qvarn-guests + AND admin creates repository qvarn + AND admin sets qvarn config guests to qvarn-guests + THEN gabriella can clone qvarn + WHEN gabriella creates qvarn branch gabriella/bugfix + AND gabriella changes qvarn branch gabriella/bugfix + THEN gabriella can push qvarn + WHEN gabriella tags qvarn gabriella/bugfix branch with gabriella/works + THEN gabriella can push qvarn with tags + FINALLY admin removes things that were created + +Steven can't read the ops/secrets repo +----------------------------------------------------------------------------- + + SCENARIO Steven can't clone ops/secrets + WHEN admin creates user steven + AND admin creates repository ops/secrets + THEN steven cannot clone ops/secrets + FINALLY admin removes things that were created + +A public repository can be clone with the anonymous git protocol +----------------------------------------------------------------------------- + + SCENARIO everyone can clone a public repository + WHEN admin creates repository qvarn + AND admin sets qvarn config public to yes + THEN we can clone qvarn via the git protocol + FINALLY admin removes things that were created + +Scenario step implementations +============================================================================= + +WHEN admin creates user +----------------------------------------------------------------------------- + + IMPLEMENTS WHEN admin creates user (\S+) + username = helper.get_next_match() + # FIXME: Create first, this is temporary + helper.append_to_list('users', username) + helper.gitano( + None, + 'user add {} {}@example.com Test {}'.format( + username, username, username)) + pubkey = helper.ssh_keygen(username) + helper.gitano(None, + 'as {} sshkey add default'.format(username), stdin=pubkey) + +WHEN admin creates group +----------------------------------------------------------------------------- + + IMPLEMENTS WHEN admin creates group (\S+) + group = helper.get_next_match() + # FIXME: Create first, this is temporary + helper.append_to_list('groups', group) + helper.gitano(None, 'group add {} Test group'.format(group)) + +WHEN admin adds user to group +----------------------------------------------------------------------------- + + IMPLEMENTS WHEN admin adds (\S+) to (\S+) + user = helper.get_next_match() + group = helper.get_next_match() + output = helper.gitano( + None, 'group adduser {} {}'.format(group, user)) + +WHEN admin created repository +----------------------------------------------------------------------------- + + IMPLEMENTS WHEN admin creates repository (\S+) + repo = helper.get_next_match() + # FIXME: Create first, this is temporary + helper.append_to_list('repositories', repo) + helper.gitano(None, 'create {}'.format(repo)) + + # Make a commit in master in the repo, push that. + user = 'admin' + url = helper.repo_ssh_url(repo) + dirname = helper.local_checkout_dirname(user, repo) + helper.git_as_checked(None, ['clone', url, dirname]) + cliapp.runcmd(['git', 'config', 'user.email', user], cwd=dirname) + cliapp.runcmd(['git', 'config', 'user.name', user], cwd=dirname) + with open(os.path.join(dirname, 'foo.txt'), 'a') as f: + f.write('') + cliapp.runcmd(['git', 'add', 'foo.txt'], cwd=dirname) + cliapp.runcmd(['git', 'commit', '-mfoo'], cwd=dirname) + helper.git_as_checked(None, ['push', '--all'], cwd=dirname) + +WHEN admin sets repository config +----------------------------------------------------------------------------- + + IMPLEMENTS WHEN admin sets (\S+) config (\S+) to (\S+) + repo = helper.get_next_match() + key = helper.get_next_match() + value = helper.get_next_match() + helper.gitano(None, 'config {} set {} {}'.format(repo, key, value)) + +THEN a user can clone a repository +----------------------------------------------------------------------------- + + IMPLEMENTS THEN (\S+) can clone (\S+) + user = helper.get_next_match() + repo = helper.get_next_match() + url = helper.repo_ssh_url(repo) + dirname = helper.local_checkout_dirname(user, repo) + helper.git_as_checked(user, ['clone', url, dirname]) + cliapp.runcmd(['git', 'config', 'user.email', user], cwd=dirname) + cliapp.runcmd(['git', 'config', 'user.name', user], cwd=dirname) + +THEN a user can't clone a repository +----------------------------------------------------------------------------- + + IMPLEMENTS THEN (\S+) cannot clone (\S+) + user = helper.get_next_match() + repo = helper.get_next_match() + url = helper.repo_ssh_url(repo) + dirname = helper.local_checkout_dirname(user, repo) + exit, out, err = helper.git_as(user, ['clone', url, dirname]) + helper.assertNotEqual(exit, 0) + +THEN a repository can be cloned over the git protocol +----------------------------------------------------------------------------- + + IMPLEMENTS THEN we can clone (\S+) via the git protocol + repo = helper.get_next_match() + server = os.environ['GITANO_SERVER'] + url = 'git://{}/{}'.format(server, repo) + dirname = helper.local_checkout_dirname('anonymous', repo) + cliapp.runcmd(['git', 'clone', url, dirname]) + +WHEN a user creates a local branch +----------------------------------------------------------------------------- + + IMPLEMENTS WHEN (\S+) creates (\S+) branch (\S+) + user = helper.get_next_match() + repo = helper.get_next_match() + branch = helper.get_next_match() + dirname = helper.local_checkout_dirname(user, repo) + cliapp.runcmd(['git', 'checkout', '-b', branch], cwd=dirname) + +WHEN a user changes a branch locally +----------------------------------------------------------------------------- + + IMPLEMENTS WHEN (\S+) changes (\S+) branch (\S+) + user = helper.get_next_match() + repo = helper.get_next_match() + branch = helper.get_next_match() + dirname = helper.local_checkout_dirname(user, repo) + with open(os.path.join(dirname, 'foo.txt'), 'a') as f: + f.write('foo\n') + cliapp.runcmd(['git', 'add', 'foo.txt'], cwd=dirname) + cliapp.runcmd(['git', 'commit', '-mfoo'], cwd=dirname) + +THEN a user can push all local branches +----------------------------------------------------------------------------- + + IMPLEMENTS THEN (\S+) can push (\S+) + user = helper.get_next_match() + repo = helper.get_next_match() + url = helper.repo_ssh_url(repo) + dirname = helper.local_checkout_dirname(user, repo) + helper.git_as_checked(user, ['push', '--all', 'origin'], cwd=dirname) + +THEN a user can push all local tags +----------------------------------------------------------------------------- + + IMPLEMENTS THEN (\S+) can push (\S+) with tags + user = helper.get_next_match() + repo = helper.get_next_match() + url = helper.repo_ssh_url(repo) + dirname = helper.local_checkout_dirname(user, repo) + helper.git_as_checked(user, ['push', '--tags', 'origin'], cwd=dirname) + +THEN a user can't push local branches +----------------------------------------------------------------------------- + + IMPLEMENTS THEN (\S+) cannot push (\S+) + user = helper.get_next_match() + repo = helper.get_next_match() + dirname = helper.local_checkout_dirname(user, repo) + exit, out, err = helper.git_as( + user, + ['push', '--all', 'origin'], cwd=dirname) + sys.stdout.write(out) + sys.stderr.write(err) + helper.assertNotEqual(exit, 0) + +THEN a user can't push local tags +----------------------------------------------------------------------------- + + IMPLEMENTS THEN (\S+) cannot push (\S+) with tags + user = helper.get_next_match() + repo = helper.get_next_match() + dirname = helper.local_checkout_dirname(user, repo) + exit, out, err = helper.git_as( + user, + ['push', '--tags', 'origin'], cwd=dirname) + helper.assertNotEqual(exit, 0) + +WHEN a user merges a branch locally +----------------------------------------------------------------------------- + + IMPLEMENTS WHEN (\S+) merges (\S+) branch (\S+) to (\S+) + user = helper.get_next_match() + repo = helper.get_next_match() + branch_from = helper.get_next_match() + branch_to = helper.get_next_match() + dirname = helper.local_checkout_dirname(user, repo) + cliapp.runcmd(['git', 'checkout', branch_to], cwd=dirname) + cliapp.runcmd(['git', 'merge', branch_from], cwd=dirname) + +WHEN a user creates a local tag +----------------------------------------------------------------------------- + + IMPLEMENTS WHEN (\S+) tags (\S+) (\S+) branch with (\S+) + user = helper.get_next_match() + repo = helper.get_next_match() + branch = helper.get_next_match() + tag = helper.get_next_match() + dirname = helper.local_checkout_dirname(user, repo) + cliapp.runcmd(['git', 'tag', '-mrelease!', tag], cwd=dirname) + +FINALLY clean up anything created during tests +----------------------------------------------------------------------------- + + IMPLEMENTS FINALLY admin removes things that were created + def iter(var, prefix): + items = helper.get_variable(var, []) + for item in items: + helper.gitano_confirm_with_token(prefix, item) + iter('users', 'user del') + iter('groups', 'group del') + iter('repositories', 'destroy') |