carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux && rootrunner
2
3package host
4
5import (
6 "crypto/ed25519"
7 "encoding/base64"
8 "encoding/json"
9 "fmt"
10 "io"
11 "net"
12 "net/http"
13 "net/url"
14 "os"
15 "path/filepath"
16 "strings"
17 "sync/atomic"
18 "testing"
19 "time"
20
21 "github.com/carlosframework/platform/internal/console"
22 "github.com/carlosframework/platform/internal/provision"
23)
24
25func plan14ConsolePayload() {
26 st, err := console.Open(os.Getenv("CONSOLE_DB"))
27 if err != nil {
28 fmt.Fprintln(os.Stderr, err)
29 os.Exit(1)
30 }
31 defer st.Close()
32 srv, err := console.NewServer(console.Config{Origin: "http://tenant", Keymail: os.Getenv("CONSOLE_KEYMAIL")}, st)
33 if err != nil {
34 fmt.Fprintln(os.Stderr, err)
35 os.Exit(1)
36 }
37 ln, err := net.Listen("tcp", os.Getenv("CONSOLE_ADDR"))
38 if err == nil {
39 err = http.Serve(ln, srv)
40 }
41 fmt.Fprintln(os.Stderr, err)
42 os.Exit(1)
43}
44
45func plan14ConsoleProgram(t *testing.T, host, endpoint string) (claimUnitProgram, func(*http.Client)) {
46 t.Helper()
47 ln, err := net.Listen("tcp", "127.0.0.1:0")
48 if err != nil {
49 t.Fatal(err)
50 }
51 var contacted atomic.Int64
52 srv := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
53 contacted.Add(1)
54 w.WriteHeader(http.StatusBadRequest)
55 })}
56 go srv.Serve(ln)
57 t.Cleanup(func() { srv.Close() })
58 keymail := "http://" + ln.Addr().String()
59 p := claimUnitProgram{Schema: 1, Host: host, Args: []string{"plan14-console"}, Env: []string{
60 "PLAN14_CONSOLE=1", "CONSOLE_ADDR=" + endpoint, "CONSOLE_DB=" + provision.NestedDBPath("/data", host), "CONSOLE_KEYMAIL=" + keymail,
61 }}
62 device := ""
63 return p, func(c *http.Client) {
64 t.Helper()
65 check := func(path, body string) map[string]any {
66 t.Helper()
67 resp, err := c.Post("http://tenant"+path, "application/json", strings.NewReader(body))
68 if err != nil {
69 t.Fatal(err)
70 }
71 defer resp.Body.Close()
72 var result map[string]any
73 if err := json.NewDecoder(resp.Body).Decode(&result); err != nil || resp.StatusCode != http.StatusOK {
74 t.Fatal(path, resp.Status, result, err)
75 }
76 return result
77 }
78 if device == "" {
79 started := check("/auth/cli/start", `{"label":"reserved console"}`)
80 device, _ = started["device_code"].(string)
81 if device == "" {
82 t.Fatal("console did not write login request", started)
83 }
84 }
85 body, _ := json.Marshal(map[string]string{"device_code": device})
86 result := check("/auth/cli/poll", string(body))
87 if result["status"] != "pending" {
88 t.Fatal("console lost persistent login request", result)
89 }
90 client := *c
91 client.CheckRedirect = func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }
92 resp, err := client.Get("http://tenant/auth/signin")
93 if err != nil {
94 t.Fatal(err)
95 }
96 resp.Body.Close()
97 location, err := url.Parse(resp.Header.Get("Location"))
98 if err != nil || resp.StatusCode != http.StatusSeeOther || !strings.HasPrefix(location.String(), keymail+"/") {
99 t.Fatal("console sign-in did not select the test upstream", err, resp.Status)
100 }
101 req, _ := http.NewRequest(http.MethodGet, "http://tenant/auth/callback?code=runner&state="+url.QueryEscape(location.Query().Get("state")), nil)
102 for _, cookie := range resp.Cookies() {
103 req.AddCookie(cookie)
104 }
105 resp, err = client.Do(req)
106 if err != nil {
107 t.Fatal("console exchange", err)
108 }
109 io.Copy(io.Discard, resp.Body)
110 resp.Body.Close()
111 if resp.StatusCode != http.StatusBadGateway || contacted.Load() != 0 {
112 t.Fatal("console reached a sibling TCP upstream", resp.Status, contacted.Load())
113 }
114 control := &http.Client{Timeout: time.Second}
115 resp, err = control.Get(keymail)
116 if err != nil {
117 t.Fatal("upstream control", err)
118 }
119 resp.Body.Close()
120 if contacted.Swap(0) != 1 {
121 t.Fatal("control did not reach upstream")
122 }
123 }
124}
125
126func plan14ConsoleBinaryProgram(t *testing.T, p claimUnitProgram) claimUnitProgram {
127 t.Helper()
128 pub, _, err := ed25519.GenerateKey(nil)
129 if err != nil {
130 t.Fatal(err)
131 }
132 p.Args = []string{"serve"}
133 p.Env = p.Env[1:]
134 p.Env = append(p.Env, "CONSOLE_ORIGIN=http://tenant", "CARLOS_RELEASE_PUBKEY="+base64.StdEncoding.EncodeToString(pub), "CARLOS_DEPLOYMENT_DIR="+filepath.Join(provision.InstanceDir("/data", p.Host), "deployment"), "AWS_EC2_METADATA_DISABLED=true")
135 return p
136}
137