summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2017-07-16 14:31:02 +0300
committerLars Wirzenius <liw@liw.fi>2017-07-16 14:31:02 +0300
commitec8822d3084838427abbdfcaddb6037c78ccec02 (patch)
tree0cda33834eb55ea99210a2b186b231c618d9a824
parent34601e01463c913c19420dfb4bd56ce0804961fe (diff)
downloadgit.liw.fi-ruleset-tests-ec8822d3084838427abbdfcaddb6037c78ccec02.tar.gz
Add: yarns for gitano ruleset
-rw-r--r--yarns.mdwn432
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')