Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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.

forge/repo/repo.go +14

@@ -16,6 +16,7 @@ import (
16 16
17 17 "github.com/rodent-software/vole/core/git"
18 18 "github.com/rodent-software/vole/forge/access"
19 "github.com/rodent-software/vole/forge/secret"
19 20 )
20 21
21 22 const (
@@ -53,6 +54,9 @@ type Store struct {
53 54 // configuration the instance owns, keyed by account name, read on every push and
54 55 // every request (see the access package).
55 56 ACLs *access.Store
57 // Secrets is where the repository's sealed values live, keyed by the same stable
58 // id the rules use. A deleted repository takes its secrets with it.
59 Secrets *secret.Store
56 60 }
57 61
58 62 func New(root, binPath string, acls *access.Store) *Store {
@@ -272,9 +276,19 @@ func (s *Store) Delete(name, actor string) error {
272 276 return err
273 277 }
274 278 }
279 if s.Secrets != nil {
280 if err := s.Secrets.RecordRepoDelete(m.ID, actor); err != nil {
281 return err
282 }
283 }
275 284 if err := os.RemoveAll(m.Dir); err != nil {
276 285 return err
277 286 }
287 if s.Secrets != nil {
288 if err := s.Secrets.DeleteRepo(m.ID); err != nil {
289 return err
290 }
291 }
278 292 if s.ACLs != nil {
279 293 return s.ACLs.Delete(m.ID)
280 294 }