For reading and verification only. You may not copy, use, modify or redistribute this code. Full notice on the main page.
tunnel/statement_test.go
| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | "time" |
| 7 | ) |
| 8 | |
| 9 | // STATEMENT: the control plane's signed access statement, forwarded as it is, right after the OK, before any stream can be opened on the connection. |
| 10 | |
| 11 | const testStatement = "eyJhbGciOiJFZERTQSIsInR5cCI6ImRldmljZS1hY2Nlc3MifQ.eyJzZXEiOjEyMzQ1fQ.c2lnbmF0dXJlLXNpZ25hdHVyZS1zaWduYXR1cmUtc2lnbmF0dXJl" |
| 12 | |
| 13 | func statementPlan(e *testEnv, d *testDevice, access string) *devicePlan { |
| 14 | return &devicePlan{host: d.host, answer: func(int) AuthorizeResponse { |
| 15 | now := e.now() |
| 16 | return AuthorizeResponse{DeviceID: d.id, Label: d.label, Host: d.host, Status: statusActive, Plan: "paid", ValidUntil: now.Unix() + 3600, ServerTime: now.Unix(), |
| 17 | Policy: okPolicy{20, 45, 1800, 14400, 300}, Nodes: []any{}, Access: access} |
| 18 | }} |
| 19 | } |
| 20 | |
| 21 | func TestStatement_IsForwardedUnchangedRightAfterTheOKOfAHelloAndOfARenew(t *testing.T) { |
| 22 | e := newTestEnv(t, nil) |
| 23 | d := e.newDeviceNoPlan() |
| 24 | e.cp.set(d.id, statementPlan(e, d, testStatement)) |
| 25 | dc := e.connect(d) |
| 26 | dc.session = e.session(d, nil) |
| 27 | dc.send("HELLO 2 " + dc.session) |
| 28 | m, _ := parseLine(dc.recv(), dirN2D) |
| 29 | dc.send("PROOF " + e.proof(d, dc.session, m.str("nonce"), nil)) |
| 30 | if line := dc.recv(); !strings.HasPrefix(line, "OK ") { |
| 31 | t.Fatalf("%q", line) |
| 32 | } |
| 33 | if line := dc.recv(); line != "STATEMENT "+testStatement { |
| 34 | t.Fatalf("the line after the OK: %q", line) |
| 35 | } |
| 36 | // a client stream opened now reaches a device that has the statement already: the OPEN comes after it |
| 37 | ds := dc.serve(nil) |
| 38 | c, err := e.clientTLS(d) |
| 39 | if err != nil { |
| 40 | t.Fatal(err) |
| 41 | } |
| 42 | _ = c.Close() |
| 43 | if ds.openCount() != 1 { |
| 44 | t.Fatalf("opens %d", ds.openCount()) |
| 45 | } |
| 46 | |
| 47 | // and at every RENEW, with the control plane's newer statement |
| 48 | const newer = "eyJhbGciOiJFZERTQSJ9.eyJzZXEiOjEyMzQ2fQ.c2lnbmF0dXJl" |
| 49 | e.cp.set(d.id, statementPlan(e, d, newer)) |
| 50 | if r := dc.renew(); !strings.HasPrefix(r, "OK ") { |
| 51 | t.Fatalf("%q", r) |
| 52 | } |
| 53 | eventually(t, "the newer statement", 2*time.Second, func() bool { return containsLine(ds.otherLines(), "STATEMENT "+newer) }) |
| 54 | e.checkTranscript() |
| 55 | } |
| 56 | |
| 57 | func TestStatement_NoStatementNoLineAndABadOneIsDroppedNeverRelayedNeverAPanic(t *testing.T) { |
| 58 | e := newTestEnv(t, nil) |
| 59 | for name, access := range map[string]string{ |
| 60 | "none": "", |
| 61 | "not a jws": "this is not a token", |
| 62 | "two segments": "aaaa.bbbb", |
| 63 | "too long": strings.Repeat("a", policyData.Protocol.StatementLineMaxBytes) + ".b.c", |
| 64 | "a space in it": "aaaa.bb bb.cccc", |
| 65 | } { |
| 66 | d := e.newDeviceNoPlan() |
| 67 | e.cp.set(d.id, statementPlan(e, d, access)) |
| 68 | dc := e.connectOK(d) |
| 69 | ds := dc.serve(nil) |
| 70 | time.Sleep(60 * time.Millisecond) |
| 71 | for _, line := range ds.otherLines() { |
| 72 | if strings.HasPrefix(line, "STATEMENT") { |
| 73 | t.Errorf("%s: forwarded %.40q", name, line) |
| 74 | } |
| 75 | } |
| 76 | if !e.srv.Online(d.host) { |
| 77 | t.Errorf("%s: the connection did not come up", name) |
| 78 | } |
| 79 | } |
| 80 | // the longest line the grammar allows is forwarded |
| 81 | d := e.newDeviceNoPlan() |
| 82 | longest := strings.Repeat("a", policyData.Protocol.StatementLineMaxBytes-len("STATEMENT ")-1-4) + ".b.c" |
| 83 | if statementLine(longest) == "" { |
| 84 | t.Fatal("the longest statement the grammar allows was refused") |
| 85 | } |
| 86 | e.cp.set(d.id, statementPlan(e, d, longest)) |
| 87 | dc := e.connectOK(d) |
| 88 | ds := dc.serve(nil) |
| 89 | eventually(t, "the longest statement", 3*time.Second, func() bool { return containsLine(ds.otherLines(), "STATEMENT "+longest) }) |
| 90 | } |
| 91 | |
| 92 | func TestStatement_AnAccessTheControlPlaneSendsThatTheGrammarRefusesIsABadAnswerOverHTTP(t *testing.T) { |
| 93 | bad := `{"ok":true,"v":1,"device_id":"` + randomHex(16) + `","label":"` + label20() + `","host":"x","status":"active","plan":"paid","valid_until":1793000000,"server_time":1793000000,` + |
| 94 | `"policy":{"ping_interval_s":20,"dead_peer_s":45,"stream_idle_s":1800,"stream_max_age_s":14400,"renew_before_valid_until_s":300},"nodes":[],"access":"not a jws"}` |
| 95 | if _, err := parseAuthorizeResponse([]byte(bad)); err == nil { |
| 96 | t.Fatal("an answer with a statement that is not a JWS was accepted") |
| 97 | } |
| 98 | } |