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.

Blame · client/secret.go

Line attribution on main · 87 line(s)

0c40a1d3
Keenan Nemetz
7d ago
1 package client
2
3 // Repository secrets over the instance's SSH verb.
4 //
5 // The values live in the instance's own store, not in a clone, so every question
6 // about them is a question for the server: the CLI sends `vole-secret` and the
7 // server checks the maintainer capability and records the read. The value of a
8 // `set` travels on stdin, never in the command line, because the exec line is
9 // visible to every process on both hosts and the instance records the command it
10 // ran.
11
12 import (
13 "fmt"
14 "strings"
15 )
16
17 // SetSecret stores a value and returns the server's one-line answer.
18 func SetSecret(gitDir, remote, repoName, name string, value []byte) (string, error) {
19 host, port, verb, err := secretTarget(gitDir, remote, "set", repoName, name)
20 if err != nil {
21 return "", err
22 }
23 return runSSH(host, port, verb, strings.NewReader(string(value)))
24 }
25
26 // GetSecret returns a stored value exactly as it was written, with nothing
27 // appended. A caller that redirects it to a file gets the bytes that were stored.
28 func GetSecret(gitDir, remote, repoName, name string) (string, error) {
29 host, port, verb, err := secretTarget(gitDir, remote, "get", repoName, name)
30 if err != nil {
31 return "", err
32 }
33 return runSSH(host, port, verb, nil)
34 }
35
36 // ListSecrets returns the instance's rendering of a repository's secret names,
37 // versions and read times. It never includes a value.
38 func ListSecrets(gitDir, remote, repoName string) (string, error) {
39 host, port, verb, err := secretTarget(gitDir, remote, "list", repoName, "")
40 if err != nil {
41 return "", err
42 }
43 return runSSH(host, port, verb, nil)
44 }
45
46 // RemoveSecret forgets one value and returns the server's one-line answer.
47 func RemoveSecret(gitDir, remote, repoName, name string) (string, error) {
48 host, port, verb, err := secretTarget(gitDir, remote, "rm", repoName, name)
49 if err != nil {
50 return "", err
51 }
52 return runSSH(host, port, verb, nil)
53 }
54
55 // secretTarget resolves the instance from the remote and names the repository on
56 // it. The remote supplies the host and port; the repository is an argument, so a
57 // maintainer of two repositories can manage either from one clone, and the server
58 // checks the capability for the repository that was named.
59 func secretTarget(gitDir, remote, op, repoName, name string) (host, port, verb string, err error) {
60 if remote == "" {
61 // A developer's clone already names the instance they push to, so the
62 // default is the remote they would push to as well.
63 remote = "origin"
64 }
65 host, port, _, err = SSHTarget(gitDir, remote)
66 if err != nil {
67 return "", "", "", err
68 }
69 if !validRepoArgument(repoName) {
70 return "", "", "", fmt.Errorf("%q is not a repository name", repoName)
71 }
72 args := []string{"vole-secret", op, "/" + repoName + ".git"}
73 if name != "" {
74 args = append(args, name)
75 }
76 quoted := make([]string, len(args))
77 for i, arg := range args {
78 quoted[i] = quoteSSHArg(arg)
79 }
80 return host, port, strings.Join(quoted, " "), nil
81 }
82
83 // validRepoArgument is the cheap shape check: the instance's own name rule is the
84 // authority, and it reports the problem in the server's words.
85 func validRepoArgument(name string) bool {
86 return name != "" && !strings.ContainsAny(name, "/\\ \t\n\x00") && !strings.Contains(name, "..")
87 }