vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
Store repository secrets encrypted, with a CLI and a settings form
0c40a1d3b48eb0ade37334f62ee526c371ecb61c · Keenan Nemetz · 7d ago · parent 122cbe4f
Store repository secrets encrypted, with a CLI and a settings form A repository has values that cannot live in its code: a deploy key, a registry token, a staging password. They are now stored in data/secrets.db, sealed with AES-256-GCM under data/secrets.key, and read through one store that records every read and every change. The key is a separate file, and secrets_key can move it, so a backup of the data directory can be a backup of ciphertext rather than of plaintext. `vole secret list|set|get|rm` asks the instance over the key the caller already pushes with, since the store is server-side. set reads the value from stdin or a file and never from the argument list; get writes the value and nothing else. The maintainer role is the capability: the accounts that approve work and allow CI own the credentials those jobs will use. The repository's Settings tab gains the same list — names, versions, who changed one, when a value was last read — with set, replace and delete. It never renders a value, and a maintainer who is not an admin now reaches that page for this section alone. Values are limited to what one environment variable can carry: 128 KiB for NAME=value, valid UTF-8 and no NUL. A longer value could never reach a step, and the refusal says so. Slice 1 of #154.
server/web/page.go +5−1
| @@ -34,6 +34,10 @@ type Page struct { | |||
| 34 | 34 | // CanAdmin reports whether the viewer may administer the repository named in | |
| 35 | 35 | // the request path. The repository header uses it to show Settings. | |
| 36 | 36 | CanAdmin bool | |
| 37 | // CanMaintain reports whether the viewer holds the maintainer capability. The | ||
| 38 | // header shows Settings for a maintainer because that page holds the | ||
| 39 | // repository's secrets, and a maintainer is who owns them. | ||
| 40 | CanMaintain bool | ||
| 37 | 41 | // CloneURL and SSHURL are the two transports the repository header offers to | |
| 38 | 42 | // copy. They are empty when the path does not name a repository. | |
| 39 | 43 | CloneURL string | |
| @@ -130,7 +134,7 @@ func (w *Web) userRef(name string) userRef { | |||
| 130 | 134 | // Templates call it as | |
| 131 | 135 | // {{template "repohead" (nav .Page "issues")}}. | |
| 132 | 136 | func (w *Web) repoNav(p Page, tab string) repoNavData { | |
| 133 | n := repoNavData{Name: p.RepoName, Tab: tab, Private: true, CanAdmin: p.CanAdmin, | ||
| 137 | n := repoNavData{Name: p.RepoName, Tab: tab, Private: true, CanAdmin: p.CanMaintain || p.CanAdmin, | ||
| 134 | 138 | CloneURL: p.CloneURL, SSHURL: p.SSHURL, Site: p.Site} | |
| 135 | 139 | if p.RepoName == "" { | |
| 136 | 140 | return n | |