internal/control/deploykey.go
135 lines · 3914 bytes
1package control
2
3import (
4 "errors"
5 "fmt"
6 "io"
7
8 "golang.org/x/crypto/ssh"
9
10 "gitbay.org/gitbay/internal/policy"
11 "gitbay.org/gitbay/internal/protocol"
12 "gitbay.org/gitbay/internal/store"
13)
14
15func init() {
16 register(Command{Path: []string{"repo", "deploy-key", "add"},
17 Summary: "bind a read-only (or --rw) key to one repository",
18 Usage: "repo deploy-key add <owner/name> [--rw] < key.pub",
19 Flags: []Flag{
20 {"--rw", "", "the key may push, not just fetch", ""},
21 },
22 Examples: []string{"repo deploy-key add krz/gitbay < key.pub"},
23 ReadsStdin: true,
24 MintsCredential: true, Run: runDeployKeyAdd})
25 register(Command{Path: []string{"repo", "deploy-key", "list"},
26 Summary: "list deploy keys",
27 Usage: "repo deploy-key list <owner/name>",
28 Examples: []string{"repo deploy-key list krz/gitbay"},
29 ReadOnly: true, Run: runDeployKeyList})
30 register(Command{Path: []string{"repo", "deploy-key", "remove"},
31 Summary: "remove a deploy key",
32 Usage: "repo deploy-key remove <owner/name> <fingerprint>",
33 Examples: []string{"repo deploy-key remove krz/gitbay SHA256:abcd1234"},
34 Run: runDeployKeyRemove})
35}
36
37func runDeployKeyAdd(c *Ctx, args []string) int {
38 mode := "ro"
39 var path string
40 for _, a := range args {
41 switch a {
42 case "--rw":
43 mode = "rw"
44 default:
45 if path != "" {
46 return c.usage()
47 }
48 path = a
49 }
50 }
51 if path == "" {
52 return c.usage()
53 }
54 repo, code := resolveRepo(c, path, policy.CanAdmin)
55 if code >= 0 {
56 return code
57 }
58 raw, err := io.ReadAll(io.LimitReader(c.Stdin, 64<<10))
59 if err != nil {
60 return c.fail(protocol.ExitFailure, "reading key: %v", err)
61 }
62 pub, comment, _, _, err := ssh.ParseAuthorizedKey(raw)
63 if err != nil {
64 return c.fail(protocol.ExitUsage, "not a valid public key in authorized_keys format: %v", err)
65 }
66 label, err := keyLabel(comment)
67 if err != nil {
68 return c.fail(protocol.ExitUsage, "%v", err)
69 }
70 fp := ssh.FingerprintSHA256(pub)
71 scope := fmt.Sprintf("deploy:%d:%s", repo.ID, mode)
72 if err := c.Store.AddSSHKeyFrom(c.User.ID, fp, pub.Type(), pub.Marshal(), scope, label, store.KeyOrigin{CreatedByToken: c.TokenID}); err != nil {
73 if errors.Is(err, store.ErrDuplicateKey) {
74 return c.failErr(err)
75 }
76 return c.fail(protocol.ExitFailure, "%v", err)
77 }
78 return c.emit(map[string]string{"fingerprint": fp, "mode": mode}, func(w io.Writer) {
79 fmt.Fprintf(w, "deploy key %s (%s) bound to %s\n", fp, mode, repo.Path())
80 })
81}
82
83func runDeployKeyList(c *Ctx, args []string) int {
84 if len(args) != 1 {
85 return c.usage()
86 }
87 repo, code := resolveRepo(c, args[0], policy.CanAdmin)
88 if code >= 0 {
89 return code
90 }
91 keys, err := c.Store.ListDeployKeys(repo.ID)
92 if err != nil {
93 return c.fail(protocol.ExitFailure, "%v", err)
94 }
95 type out struct {
96 Fingerprint string `json:"fingerprint"`
97 Algo string `json:"algo"`
98 Mode string `json:"mode"`
99 Label string `json:"label"`
100 }
101 var ds []out
102 for _, k := range keys {
103 mode := "ro"
104 if policy.DeployScopeAllows(k.Scope, repo.ID, true) {
105 mode = "rw"
106 }
107 ds = append(ds, out{k.Fingerprint, k.Algo, mode, k.Label})
108 }
109 return c.emit(ds, func(w io.Writer) {
110 tb := c.table(w, "FINGERPRINT", "ALGO", "MODE", "LABEL")
111 for _, d := range ds {
112 tb.row(cFlex(d.Fingerprint), cText(d.Algo), cState(d.Mode), cText(d.Label))
113 }
114 tb.flush()
115 })
116}
117
118func runDeployKeyRemove(c *Ctx, args []string) int {
119 if len(args) != 2 {
120 return c.usage()
121 }
122 repo, code := resolveRepo(c, args[0], policy.CanAdmin)
123 if code >= 0 {
124 return code
125 }
126 if err := c.Store.RemoveDeployKey(repo.ID, args[1]); err != nil {
127 if errors.Is(err, store.ErrNotFound) {
128 return c.fail(protocol.ExitNotFound, "no deploy key %s on %s", args[1], repo.Path())
129 }
130 return c.fail(protocol.ExitFailure, "%v", err)
131 }
132 return c.emit(map[string]string{"removed": args[1]}, func(w io.Writer) {
133 fmt.Fprintf(w, "removed deploy key %s from %s\n", args[1], repo.Path())
134 })
135}