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.
client/remote.go +12
| @@ -3,6 +3,7 @@ package client | |||
| 3 | 3 | import ( | |
| 4 | 4 | "errors" | |
| 5 | 5 | "fmt" | |
| 6 | "io" | ||
| 6 | 7 | "net/url" | |
| 7 | 8 | "os" | |
| 8 | 9 | "os/exec" | |
| @@ -148,7 +149,15 @@ func SSHTarget(gitDir, remote string) (host, port, path string, err error) { | |||
| 148 | 149 | // RunSSH runs one of the instance's read-only verbs over the key the caller | |
| 149 | 150 | // already pushes with. No new endpoint, no second credential. | |
| 150 | 151 | func RunSSH(host, port, verb string) (string, error) { | |
| 152 | return runSSH(host, port, verb, nil) | ||
| 153 | } | ||
| 154 | |||
| 155 | // runSSH is RunSSH with an optional stdin. The secret verbs carry their value | ||
| 156 | // this way: an exec line is visible to every process on both hosts, and the | ||
| 157 | // instance keeps the command it ran. | ||
| 158 | func runSSH(host, port, verb string, stdin io.Reader) (string, error) { | ||
| 151 | 159 | cmd := SSHCommand(host, port, verb) | |
| 160 | cmd.Stdin = stdin | ||
| 152 | 161 | var out, stderr strings.Builder | |
| 153 | 162 | cmd.Stdout, cmd.Stderr = &out, &stderr | |
| 154 | 163 | if err := cmd.Run(); err != nil { | |
| @@ -159,6 +168,9 @@ func RunSSH(host, port, verb string) (string, error) { | |||
| 159 | 168 | } | |
| 160 | 169 | return "", fmt.Errorf("%s: %w: %s", host, err, strings.TrimSpace(stderr.String())) | |
| 161 | 170 | } | |
| 171 | // The output is returned as it arrived. A verb that answers with a value must | ||
| 172 | // not have a byte trimmed off it, and a verb that answers with lines has its | ||
| 173 | // whitespace handled by the caller that knows what the lines are. | ||
| 162 | 174 | return out.String(), nil | |
| 163 | 175 | } | |
| 164 | 176 | ||