--- title: "A Gitano ruleset: tests" author: Lars Wirzenius (liw@liw.fi) date: 2017-02-04 ... [Gitano][] is the server software I use for my personal git server, and also for work. One of Gitano's primary features is a strong language for defining access control, called [Lace][]. However, with great power comes long shoelaces to tie together. This document explains what my rules set 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/ 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. * **Tina Thirdparty** is an outside developer working on Qvarn as a full member, and needs full write access to the repository. * **J. R. Hacker** is an outsider who contributes to the projects, but has no need to commit to any the repositories. Instead, they maintain their own repository elsewhere, and Ian or Tina access that. * **CI** is an automated system that monitors some repositories to build, test, and deliver software. Use cases ----------------------------------------------------------------------------- * Ian fixes a bug in Qvarn. (Or adds a feature: same process.) * Ian clones Qvarn to his laptop, or updates his existing clone. * Ian creates a branch in the local clone and makes changes. * Ian pushes the branch to the git server. * After others have reviewed his changes, Ian merges them to master, and pushes master to the git server. * CI notices the change, runs tests, and builds and publishes a CI build of Qvarn. * Ian makes a release. * Ian clones Qvarn. * Ian tags the right commit with a release tag. * Ian pushes the tag to the git server. * CI updates its own git clone; new release tag triggers a release build. * Olive provisions a new Qvarn instance. * Olive clones a repository with Ansible playbooks. * Olive clones a repository with encrypted passowords. * Olive creates a local branch in the Ansible playbooks. * Olive makes and commits any necessary changes to the playbooks. * Olive creates a new VM and runs Ansible. * Olive pushes her new local branch to the server for review. * After revies feedback, Olive merges her changes to master. * Olive pushes master changes to server. * Steven updates internal wiki. * Steven clones intrawiki.git. * Steven makes changes. * Steven commits changes to local master branch. * Steven pushes master changes to git server. * CI notices changes and updated intrawiki website. * Gabriella makes a bug fix. * Gabriella clones qvarn.git. * Gabriella creates a new branch. * Gabriella changes new branch to fix bug. * Gabriella pushes new branch to git server. * Gabriella notifies others of new branch. * Ian reviews branch and finds it good. * Ian merges branch to master. * CI notices change in master and runs a CI pipeline. * JR makes a bug fix. * JR clones qvarn.git. * JR creates a new local branch. * JR fixes bug in local branch. * JR pushes local branch to his own git server. * JR notifies others of changes on his git server. * Ian reviews changes from JR'r server, merges them to master from git.qvarnlabs.net, and pushes new master to git.q.n. * CI notices changes to master and runs CI pipeline. * Anyone but Olive tries to clone the secrets repository * Gabriella attempts to clone the secrets repository. * The attempt fails. * Tina adds a feature to another project, matching unreleased changes to Qvarn. * Tina clones qvarn.git. * Tina clones other-project.git. * Tina makes a change in other-project.git. * Tina pushes change in master to tina-project.git. 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 at some level. Private may mean private to all staff or private to only specific people (e.g., ops secrets). * Some people will have write access to some repositories, whereas everyone else (even with Gitano accounts) have only read access to those respositories. Example, Steven doesn't need write access to the Qvarn repository, but there's no point in restricting read access, since it's a public repository. * 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. This prevents Gary from making releases, or changing master without anyone else knowing about it. Some principles/suggestions ----------------------------------------------------------------------------- * There's several groups of people. Each group needs different access. - implicitly trusted staff members (those who can do ops) - normal staff members (Steven) - outsider guest users (Gabriella) - automated systems (CI) * Three types of repositories. - completely public (qvarn.git) - private, but public to staff (intrawiki.git) - accessible to just part of staff (ops secrets) * Guests should be able to update public repos in restricted ways. - they may only create/modify branches that are not used by others, e.g., their names are prefixed by their Gitano username - they may only create tags not used by others (again, prefixed), so that they may not create release tags * Staff is either developers or otherwise trusted (i.e., can modify any branches, can make any tags), or treated the same as guests. * Even staff may only have full access to some repos (qvarn.git), but not all (ops ones). For the latter, treat them similar to guests. * CI and other automated systems only have read-only access. If an automated system needs write access later, consider the implications at that time. Rough outline for ruleset ----------------------------------------------------------------------------- * All repositories should have three configuration variables, `readers`, `writers`, and `guests`. The value of each varibable is the name of a Gitano group. Access is granted to users in the named group. * 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. Scenarios ----------------------------------------------------------------------------- These [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/ This is going to be a long scenario, but that's just so that we don't need to re-do the setup. The setup consists of creating test users, groups, and respositories. 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 create qvarn branch bugfix AND ian changes qvarn branch bugfix THEN ian can push qvarn branch bugfix FINALLY admin removes things that were created 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 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) 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)) 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)) IMPLEMENTS WHEN admin creates repository (\S+) repo = helper.get_next_match() # FIXME: Create first, this is temporary helper.append_to_list('repositories', repo) output = helper.gitano(None, 'create {}'.format(repo)) 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)) IMPLEMENTS THEN (\S+) can clone (\S+) user = helper.get_next_match() repo = helper.get_next_match() url = helper.repo_ssh_url(repo) dirname = '{}_{}'.format(user, repo) helper.git_as(user, ['clone', url, dirname]) 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 = 'anonymouse_{}'.format(repo) cliapp.runcmd(['git', 'clone', url, dirname]) 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')