164 lines
6.2 KiB
Go
164 lines
6.2 KiB
Go
package main
|
|
|
|
// The tray beside a system service: when it starts (trayFor), what its logon
|
|
// entry runs (trayRunValue), and what its page does (uitray.go) with the pipe
|
|
// replaced by a recording fake. Every value here is invented.
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func httpRec(h http.Handler, r *http.Request) *httptest.ResponseRecorder {
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
return w
|
|
}
|
|
|
|
func TestTheTrayStartsPerPersonBesideAServiceAndNeverInIt(t *testing.T) {
|
|
cases := []struct {
|
|
goos string
|
|
asService, service bool
|
|
cmd string
|
|
want trayKind
|
|
why string
|
|
}{
|
|
{"windows", true, true, "run", trayNone, "the service runs in session 0: no desktop to put an icon on"},
|
|
{"windows", false, true, "tray", trayPipe, "beside the service, each person's logon starts their own icon"},
|
|
{"windows", true, true, "tray", trayNone, "the service itself never runs the tray verb's icon"},
|
|
{"windows", false, true, "run", trayNone, "a second bastion beside the service gets no second icon"},
|
|
{"windows", false, false, "run", trayInProcess, "a bastion run as the person keeps its own icon, as before"},
|
|
{"windows", false, false, "tray", trayNone, "no service: the daemon's own icon is the tray"},
|
|
{"linux", false, true, "tray", trayNone, "only Windows has a tray"},
|
|
{"darwin", false, false, "run", trayNone, "only Windows has a tray"},
|
|
}
|
|
for _, c := range cases {
|
|
if got := trayFor(c.goos, c.asService, c.service, c.cmd); got != c.want {
|
|
t.Errorf("%s as-service=%v service=%v %s: got %d, want %d — %s",
|
|
c.goos, c.asService, c.service, c.cmd, got, c.want, c.why)
|
|
}
|
|
}
|
|
// The logon entry runs the service's own program, quoted for the space in
|
|
// Program Files, with the tray verb.
|
|
if got := trayRunValue(`C:\Program Files\Privatae\privatae-bastion.exe`); got != `"C:\Program Files\Privatae\privatae-bastion.exe" tray` {
|
|
t.Fatalf("logon entry: %s", got)
|
|
}
|
|
}
|
|
|
|
// fakePipe answers like the service would and records what the page sent.
|
|
type fakePipe struct {
|
|
sent []pipeRequest
|
|
list pipeResponse
|
|
}
|
|
|
|
func (f *fakePipe) call(req pipeRequest) (pipeResponse, error) {
|
|
f.sent = append(f.sent, req)
|
|
switch req.Op {
|
|
case "list":
|
|
return f.list, nil
|
|
case "approve":
|
|
return pipeResponse{OK: true, Message: "Approved. " + req.Host + " picks this up."}, nil
|
|
case "deny":
|
|
return pipeResponse{OK: true, Message: "Denied."}, nil
|
|
}
|
|
return pipeResponse{Error: "unknown"}, nil
|
|
}
|
|
|
|
func trayTestServer(t *testing.T, f *fakePipe) (*uiServer, string) {
|
|
t.Helper()
|
|
u := uiTestServer(t)
|
|
u.trayPipe = true
|
|
u.pipe = f.call
|
|
return u, uiSignedIn(t, u)
|
|
}
|
|
|
|
func TestTheTrayPageServesNoAdminRoute(t *testing.T) {
|
|
uiTestConfigDir(t)
|
|
f := &fakePipe{list: pipeResponse{OK: true, State: "RUNNING", StateClass: "ok"}}
|
|
u, cookie := trayTestServer(t, f)
|
|
h := u.handler()
|
|
for _, path := range []string{
|
|
"/fragments/status", "/given", "/fragments/given", "/given/switch",
|
|
"/setup", "/setup/pair", "/setup/stepup/start", "/setup/stepup/qr", "/setup/stepup/key",
|
|
"/diskmap", "/agents", "/policy", "/device/approve", "/device/deny", "/device/approve-shown",
|
|
"/fragments/context", "/context/scan",
|
|
} {
|
|
r := uiReq(http.MethodGet, path)
|
|
r.Header.Set("Cookie", cookie)
|
|
w := httpRec(h, r)
|
|
if w.Code != http.StatusNotFound {
|
|
t.Errorf("%s answered %d on the tray's page; it runs as the person and must not mount the machine's console", path, w.Code)
|
|
}
|
|
}
|
|
r := uiReq(http.MethodGet, "/approvals")
|
|
r.Header.Set("Cookie", cookie)
|
|
if w := httpRec(h, r); w.Code != http.StatusOK {
|
|
t.Fatalf("/approvals = %d: the page itself does not serve, so the checks above proved nothing", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestTheTrayPageSendsTheCodeForTheRequestItShowed(t *testing.T) {
|
|
uiTestConfigDir(t)
|
|
digest := purposeDigest("dcn_store_grant|desk-7|/srv/example-a|read,search|.ssh|hourly|false|ttl:0")
|
|
f := &fakePipe{list: pipeResponse{OK: true, State: "RUNNING", StateClass: "ok", Approvals: []pipeApproval{{
|
|
ID: "a1b2c3d4e5f60718293a4b5c6d7e8f90", Short: "a1b2c3d4", Host: "tray.host.example", At: uiTestRecently(),
|
|
Lines: []string{"Your host asks to reach the folder /srv/example-a on desk-7"}, Purpose: digest,
|
|
}}}}
|
|
u, cookie := trayTestServer(t, f)
|
|
h := u.handler()
|
|
|
|
r := uiReq(http.MethodGet, "/approvals")
|
|
r.Header.Set("Cookie", cookie)
|
|
w := httpRec(h, r)
|
|
body := w.Body.String()
|
|
if !strings.Contains(body, "/srv/example-a") || !strings.Contains(body, `name="purpose" value="`+digest+`"`) {
|
|
t.Fatalf("the card must show the service's words and carry the purpose it describes:\n%s", body)
|
|
}
|
|
if strings.Contains(body, `href="/">This machine`) {
|
|
t.Fatal("the tray's page links to a console it does not serve")
|
|
}
|
|
|
|
// No code: nothing is sent.
|
|
n := len(f.sent)
|
|
uiPost(t, h, "/approve", url.Values{"host": {"tray.host.example"}, "id": {"a1b2c3d4e5f60718293a4b5c6d7e8f90"},
|
|
"purpose": {digest}, "code": {" "}}, cookie)
|
|
for _, s := range f.sent[n:] {
|
|
if s.Op == "approve" {
|
|
t.Fatal("an empty code was sent to the service")
|
|
}
|
|
}
|
|
// A code: sent with the id and the purpose shown, and the answer drawn.
|
|
w = uiPost(t, h, "/approve", url.Values{"host": {"tray.host.example"}, "id": {"a1b2c3d4e5f60718293a4b5c6d7e8f90"},
|
|
"purpose": {digest}, "code": {"123456"}}, cookie)
|
|
var got *pipeRequest
|
|
for i := range f.sent {
|
|
if f.sent[i].Op == "approve" {
|
|
got = &f.sent[i]
|
|
}
|
|
}
|
|
if got == nil || got.ID != "a1b2c3d4e5f60718293a4b5c6d7e8f90" || got.Purpose != digest || got.Code != "123456" || got.Host != "tray.host.example" {
|
|
t.Fatalf("the page must send exactly the request it showed: %+v", got)
|
|
}
|
|
if !strings.Contains(w.Body.String(), "Approved.") {
|
|
t.Fatalf("the service's answer must be shown: %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestTheTrayPageSaysWhenTheServiceCannotBeReached(t *testing.T) {
|
|
s := trayPipeStatus(func(pipeRequest) (pipeResponse, error) {
|
|
return pipeResponse{}, errTestNoService
|
|
})
|
|
if s.TrayError == "" || s.StateClass != "bad" || trayStateLineFrom(s) != "Cannot reach the bastion service" {
|
|
t.Fatalf("an unreachable service must be said, not shown as nothing waiting: %+v", s)
|
|
}
|
|
}
|
|
|
|
type testErr string
|
|
|
|
func (e testErr) Error() string { return string(e) }
|
|
|
|
const errTestNoService = testErr("the bastion service is not running")
|