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/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0: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/server.go +13−8

@@ -20,6 +20,7 @@ import (
20 20 "github.com/rodent-software/vole/forge/index"
21 21 "github.com/rodent-software/vole/forge/pageview"
22 22 "github.com/rodent-software/vole/forge/repo"
23 "github.com/rodent-software/vole/forge/secret"
23 24 "github.com/rodent-software/vole/forge/session"
24 25 "github.com/rodent-software/vole/internal/compress"
25 26 "github.com/rodent-software/vole/server/githttp"
@@ -40,13 +41,16 @@ type Server struct {
40 41 LFS *lfs.Store
41 42 Locks *lfs.LockStore
42 43 PageViews *pageview.Store
43 Pages *pages.Server
44 Proxy *githttp.Proxy
45 SSH *ssh.Server
46 Web *web.Web
47 Log *slog.Logger
48 mux *http.ServeMux
49 acme *acmeSetup
44 // Secrets is the per-repository store behind the settings page and the
45 // `vole secret` verbs. Values never leave it except to a caller it has audited.
46 Secrets *secret.Store
47 Pages *pages.Server
48 Proxy *githttp.Proxy
49 SSH *ssh.Server
50 Web *web.Web
51 Log *slog.Logger
52 mux *http.ServeMux
53 acme *acmeSetup
50 54 // metadataWake nudges the metadata event loop after a browser write. Pushes
51 55 // deliver the same information through the spool; this is the path a write made
52 56 // without a push uses.
@@ -145,6 +149,7 @@ func New(cfg *config.Config, binPath string, log *slog.Logger) (*Server, error)
145 149 wp.Locks = inst.Locks
146 150 wp.Sessions = inst.Sessions
147 151 wp.PageViews = inst.PageViews
152 wp.Secrets = inst.Secrets
148 153 wp.Logger = func(format string, args ...any) { log.Warn(fmt.Sprintf(format, args...)) }
149 154 if cfg.DevLogin != "" {
150 155 log.Warn("development login enabled", "account", cfg.DevLogin, "http_addr", cfg.HTTPAddr)
@@ -158,7 +163,7 @@ func New(cfg *config.Config, binPath string, log *slog.Logger) (*Server, error)
158 163 s := &Server{
159 164 Cfg: cfg, Repos: inst.Repos, Identity: inst.Identity, Sessions: inst.Sessions,
160 165 Runs: inst.Runs, Index: inst.Index, acme: acme,
161 LFS: inst.LFS, Locks: inst.Locks, PageViews: inst.PageViews,
166 LFS: inst.LFS, Locks: inst.Locks, PageViews: inst.PageViews, Secrets: inst.Secrets,
162 167 Proxy: proxy, SSH: ssh, Web: wp, Log: log,
163 168 metadataWake: make(chan struct{}, 1),
164 169 }