internal/control/deploykey.go

v1.36.0
gitbay/internal/control/deploykey.go history · blame · raw

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