internal/control/runnerrepo_test.go

v1.22.0
gitbay/internal/control/runnerrepo_test.go history · blame · raw

124 lines · 4886 bytes

  1package control
  2
  3import (
  4	"bytes"
  5	"strings"
  6	"testing"
  7
  8	"gitbay.org/gitbay/internal/config"
  9	"gitbay.org/gitbay/internal/protocol"
 10	"gitbay.org/gitbay/internal/store"
 11)
 12
 13// Generated once with ssh-keygen -t ed25519; a valid authorized_keys line.
 14const testRunnerPub = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILAr2r82jFsCJwsEyrEf2wgKy9Dv45xYYici6Ii7NyCS runner@test\n"
 15
 16func repoRunnerCtx(t *testing.T, st *store.Store, uid int64, admin bool, stdin string) (*Ctx, *bytes.Buffer) {
 17	t.Helper()
 18	var out bytes.Buffer
 19	return &Ctx{
 20		User:   store.User{ID: uid, Username: "alice", IsAdmin: admin},
 21		Scope:  "full",
 22		Source: "SHA256:session",
 23		Store:  st,
 24		Cfg:    config.Config{Server: config.Server{SiteURL: "https://x.test"}},
 25		Stdin:  strings.NewReader(stdin),
 26		Stdout: &out,
 27		Stderr: &out,
 28		JSON:   true,
 29	}, &out
 30}
 31
 32// A fresh key is registered on the caller's account with scope runner and
 33// attached; a second add is a no-op; list shows it; remove detaches and
 34// leaves the key on the account.
 35func TestRepoRunnerAddListRemove(t *testing.T) {
 36	st, repo, uid := newQueueTestRepo(t)
 37	c, out := repoRunnerCtx(t, st, uid, false, testRunnerPub)
 38	if code := runRepoRunnerAdd(c, []string{repo.Path()}); code != protocol.ExitOK {
 39		t.Fatalf("add: exit %d %s", code, out.String())
 40	}
 41	if !strings.Contains(out.String(), `"fingerprint":"SHA256:`) {
 42		t.Fatalf("add output: %s", out.String())
 43	}
 44	keys, _ := st.ListSSHKeys(uid)
 45	if len(keys) != 1 || keys[0].Scope != "runner" {
 46		t.Fatalf("key not registered as runner: %+v", keys)
 47	}
 48	fp := keys[0].Fingerprint
 49	c, out = repoRunnerCtx(t, st, uid, false, testRunnerPub)
 50	if code := runRepoRunnerAdd(c, []string{repo.Path()}); code != protocol.ExitOK {
 51		t.Fatalf("second add: exit %d %s", code, out.String())
 52	}
 53	c, out = repoRunnerCtx(t, st, uid, false, "")
 54	if code := runRepoRunnerList(c, []string{repo.Path()}); code != protocol.ExitOK || strings.Count(out.String(), fp) != 1 {
 55		t.Fatalf("list: exit %d %s", code, out.String())
 56	}
 57	c, out = repoRunnerCtx(t, st, uid, false, "")
 58	if code := runRepoRunnerRemove(c, []string{repo.Path(), fp}); code != protocol.ExitOK {
 59		t.Fatalf("remove: exit %d %s", code, out.String())
 60	}
 61	if ok, _ := st.RunnerAttached(keys[0].ID, repo.ID); ok {
 62		t.Fatal("still attached after remove")
 63	}
 64	if keys, _ = st.ListSSHKeys(uid); len(keys) != 1 {
 65		t.Fatal("remove dropped the key from the account")
 66	}
 67	c, out = repoRunnerCtx(t, st, uid, false, "")
 68	if code := runRepoRunnerRemove(c, []string{repo.Path(), fp}); code != protocol.ExitNotFound {
 69		t.Fatalf("remove twice: exit %d, want %d", code, protocol.ExitNotFound)
 70	}
 71}
 72
 73// A key that already exists with another scope is never promoted, and
 74// another account's runner key is refused unless the caller is an admin.
 75func TestRepoRunnerAddRefusesWrongKeys(t *testing.T) {
 76	st, repo, uid := newQueueTestRepo(t)
 77	c, _ := repoRunnerCtx(t, st, uid, false, testRunnerPub)
 78	// Register the same key as a full key first.
 79	if code := runKeysAdd(c, nil); code != protocol.ExitOK {
 80		t.Fatal("keys add failed")
 81	}
 82	c, out := repoRunnerCtx(t, st, uid, false, testRunnerPub)
 83	if code := runRepoRunnerAdd(c, []string{repo.Path()}); code != protocol.ExitDenied {
 84		t.Fatalf("full key accepted as runner: exit %d %s", code, out.String())
 85	}
 86	keys, _ := st.ListSSHKeys(uid)
 87	if keys[0].Scope != "full" {
 88		t.Fatalf("scope changed to %s", keys[0].Scope)
 89	}
 90	// Someone else's runner key.
 91	bob, _ := st.CreateUser("bob", false)
 92	if err := st.AddSSHKey(bob, "SHA256:bobrunner", "ssh-ed25519", []byte("x"), "runner", ""); err != nil {
 93		t.Fatal(err)
 94	}
 95	st.RemoveSSHKey(uid, keys[0].Fingerprint)
 96	if err := st.AddSSHKey(bob, keys[0].Fingerprint, "ssh-ed25519", keys[0].Blob, "runner", ""); err != nil {
 97		t.Fatal(err)
 98	}
 99	c, out = repoRunnerCtx(t, st, uid, false, testRunnerPub)
100	if code := runRepoRunnerAdd(c, []string{repo.Path()}); code != protocol.ExitDenied {
101		t.Fatalf("another account's key attached by a non-admin: exit %d %s", code, out.String())
102	}
103	c, out = repoRunnerCtx(t, st, uid, true, testRunnerPub)
104	if code := runRepoRunnerAdd(c, []string{repo.Path()}); code != protocol.ExitOK {
105		t.Fatalf("admin could not attach another account's runner key: exit %d %s", code, out.String())
106	}
107	// The runner clones what it builds: bob cannot read alice's private
108	// repository, so not even an admin may attach his key to it.
109	secretID, err := st.CreateRepo("user", uid, "secret", "private")
110	if err != nil {
111		t.Fatal(err)
112	}
113	secret, err := st.RepoByID(secretID)
114	if err != nil {
115		t.Fatal(err)
116	}
117	c, out = repoRunnerCtx(t, st, uid, true, testRunnerPub)
118	if code := runRepoRunnerAdd(c, []string{secret.Path()}); code != protocol.ExitDenied {
119		t.Fatalf("key attached to a repo its account cannot read: exit %d %s", code, out.String())
120	}
121	if runners, _ := st.ListRepoRunners(secret.ID); len(runners) != 0 {
122		t.Fatalf("attached anyway: %+v", runners)
123	}
124}