summaryrefslogtreecommitdiff
path: root/000.yarn
blob: 93270b11f0f9485755b89bc7aa6142ddb2d6245c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
---
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')