e2e/tokenorigin_test.go

1ed9fb9399b21da8e6cf45be8389792aac82d5bc
gitbay/e2e/tokenorigin_test.go history · blame · raw

74 lines · 2658 bytes

 1package e2e
 2
 3import (
 4	"encoding/json"
 5	"fmt"
 6	"os"
 7	"strings"
 8	"testing"
 9)
10
11// An expiring token cannot mint a credential that outlives it, and
12// revoking a token can take what it created with it (#257).
13func TestTokenDelegation(t *testing.T) {
14	t.Parallel()
15	inst := startInstanceWith(t, "[api]\nenabled = true\n")
16	aliceKey := inst.newKey(t, "alice")
17	inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
18
19	mint := func(args ...string) (token, scope string) {
20		t.Helper()
21		out, errOut, code := inst.ssh(t, aliceKey, "", append([]string{"token", "create", "--json"}, args...)...)
22		if code != 0 {
23			t.Fatalf("token create %v: %s", args, errOut)
24		}
25		var env struct {
26			Data struct {
27				Token string `json:"token"`
28				Scope string `json:"scope"`
29			} `json:"data"`
30		}
31		if err := json.Unmarshal([]byte(out), &env); err != nil {
32			t.Fatalf("token create output: %v %s", err, out)
33		}
34		return env.Data.Token, env.Data.Scope
35	}
36	if _, scope := mint("--name", "plain"); scope != "read" {
37		t.Fatalf("default scope %q, want read", scope)
38	}
39	brief, _ := mint("--name", "brief", "--scope", "full", "--ttl", "1h")
40	lasting, _ := mint("--name", "lasting", "--scope", "full")
41
42	spare := inst.newKey(t, "spare")
43	pub, err := os.ReadFile(spare + ".pub")
44	if err != nil {
45		t.Fatal(err)
46	}
47	status, body := inst.apiCall(t, brief, []string{"keys", "add"}, string(pub))
48	if status != 403 || !strings.Contains(fmt.Sprint(body["error"]), "expires") {
49		t.Fatalf("expiring token added a key: %d %v", status, body)
50	}
51	if status, _ := inst.apiCall(t, brief, []string{"whoami"}, ""); status != 200 {
52		t.Fatalf("expiring token refused a read: %d", status)
53	}
54	if status, body := inst.apiCall(t, lasting, []string{"keys", "add"}, string(pub)); status != 200 {
55		t.Fatalf("keys add: %d %v", status, body)
56	}
57	if status, body := inst.apiCall(t, lasting, []string{"token", "create", "--name", "child"}, ""); status != 200 {
58		t.Fatalf("token create: %d %v", status, body)
59	}
60	if _, errOut, code := inst.ssh(t, spare, "", "whoami"); code != 0 {
61		t.Fatalf("the added key does not work: %s", errOut)
62	}
63
64	out, errOut, code := inst.ssh(t, aliceKey, "", "token", "revoke", "lasting", "--created")
65	if code != 0 || !strings.Contains(out, "token child") || !strings.Contains(out, fingerprint(t, spare+".pub")) {
66		t.Fatalf("revoke --created: exit %d\n%s%s", code, out, errOut)
67	}
68	if _, _, code := inst.ssh(t, spare, "", "whoami"); code == 0 {
69		t.Fatal("a key the revoked token created still works")
70	}
71	if out, _, _ := inst.ssh(t, aliceKey, "", "token", "list"); strings.Contains(out, "child") {
72		t.Fatalf("the child token survived:\n%s", out)
73	}
74}