Commit ed682bb8e7
Verified · cmc
Layout: unified · split
e2e/tokenorigin_test.go added +74
| @@ -0,0 +1,74 @@ | ||
| 1 | package e2e | |
| 2 | ||
| 3 | import ( | |
| 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). | |
| 13 | func 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 | } | |