internal/control/deploykey.go

e2a32d5f8d59e4213571c602bd9009b6c8fa86ed
gitbay/internal/control/deploykey.go history · blame · raw

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}