186 lines
7.0 KiB
Go
186 lines
7.0 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestGatewayCreatesConstrainedBrowser(t *testing.T) {
|
|
var created map[string]any
|
|
docker := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
|
switch {
|
|
case request.Method == http.MethodPost && request.URL.Path == "/v1.43/containers/create":
|
|
if got := request.URL.Query().Get("name"); got != namePrefix+"account-a" {
|
|
t.Fatalf("unexpected container name %q", got)
|
|
}
|
|
if err := json.NewDecoder(request.Body).Decode(&created); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
response.WriteHeader(http.StatusCreated)
|
|
_, _ = response.Write([]byte(`{"Id":"container-id"}`))
|
|
case request.Method == http.MethodPost && request.URL.Path == "/v1.43/containers/container-id/start":
|
|
response.WriteHeader(http.StatusNoContent)
|
|
default:
|
|
t.Fatalf("unexpected Docker request %s %s", request.Method, request.URL.String())
|
|
}
|
|
}))
|
|
defer docker.Close()
|
|
|
|
handler := newGateway(dockerClient{baseURL: docker.URL + "/v1.43", client: docker.Client()}, config{
|
|
image: "registry.example/browser:1.2.3",
|
|
network: "creatorhub_browser",
|
|
})
|
|
request := httptest.NewRequest(http.MethodPost, "/v1/browsers", strings.NewReader(`{"name":"account-a","seed":1000}`))
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
|
|
if response.Code != http.StatusCreated {
|
|
t.Fatalf("expected 201, got %d: %s", response.Code, response.Body.String())
|
|
}
|
|
if created["Image"] != "registry.example/browser:1.2.3" {
|
|
t.Fatalf("gateway accepted an unexpected image: %#v", created["Image"])
|
|
}
|
|
host := created["HostConfig"].(map[string]any)
|
|
if host["NetworkMode"] != "creatorhub_browser" || host["ReadonlyRootfs"] != true {
|
|
t.Fatalf("missing container isolation: %#v", host)
|
|
}
|
|
tmpfs := host["Tmpfs"].(map[string]any)
|
|
if tmpfs["/tmp/.X11-unix"] == nil || tmpfs["/home/ubuntu"] == nil {
|
|
t.Fatalf("missing writable runtime paths: %#v", tmpfs)
|
|
}
|
|
labels := created["Labels"].(map[string]any)
|
|
if labels[managedLabel] != "true" || labels[idLabel] != "account-a" {
|
|
t.Fatalf("missing ownership labels: %#v", labels)
|
|
}
|
|
}
|
|
|
|
func TestGatewayRefusesUnmanagedContainer(t *testing.T) {
|
|
deleted := false
|
|
docker := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
|
switch request.Method {
|
|
case http.MethodGet:
|
|
_, _ = response.Write([]byte(`{"Config":{"Labels":{}}}`))
|
|
case http.MethodDelete:
|
|
deleted = true
|
|
response.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}))
|
|
defer docker.Close()
|
|
|
|
handler := newGateway(dockerClient{baseURL: docker.URL, client: docker.Client()}, config{})
|
|
request := httptest.NewRequest(http.MethodDelete, "/v1/browsers/foreign", nil)
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
|
|
if response.Code != http.StatusForbidden || deleted {
|
|
t.Fatalf("expected unmanaged container to be rejected, status=%d deleted=%v", response.Code, deleted)
|
|
}
|
|
}
|
|
|
|
func TestEnsureInternalNetwork(t *testing.T) {
|
|
var created struct {
|
|
Name string `json:"Name"`
|
|
Driver string `json:"Driver"`
|
|
Internal bool `json:"Internal"`
|
|
Options map[string]string `json:"Options"`
|
|
Labels map[string]string `json:"Labels"`
|
|
}
|
|
docker := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
|
switch request.Method {
|
|
case http.MethodGet:
|
|
response.WriteHeader(http.StatusNotFound)
|
|
case http.MethodPost:
|
|
if err := json.NewDecoder(request.Body).Decode(&created); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
response.WriteHeader(http.StatusCreated)
|
|
}
|
|
}))
|
|
defer docker.Close()
|
|
|
|
client := dockerClient{baseURL: docker.URL, client: docker.Client()}
|
|
if err := client.ensureInternalNetwork("creatorhub_browser"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if created.Name != "creatorhub_browser" || created.Driver != "bridge" || !created.Internal ||
|
|
created.Options[bridgeICCOption] != "false" || len(created.Options) != 1 ||
|
|
created.Labels[managedLabel] != "true" || created.Labels[networkRoleLabel] != browserNetworkRole {
|
|
t.Fatalf("network is not isolated: %#v", created)
|
|
}
|
|
}
|
|
|
|
func TestEnsureInternalNetworkRejectsUnsafeExistingNetwork(t *testing.T) {
|
|
valid := map[string]any{
|
|
"Name": "creatorhub_browser",
|
|
"Driver": "bridge",
|
|
"Internal": true,
|
|
"Attachable": false,
|
|
"Ingress": false,
|
|
"Options": map[string]string{bridgeICCOption: "false"},
|
|
"Labels": map[string]string{
|
|
managedLabel: "true",
|
|
networkRoleLabel: browserNetworkRole,
|
|
},
|
|
}
|
|
validDocker := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) {
|
|
_ = json.NewEncoder(response).Encode(valid)
|
|
}))
|
|
defer validDocker.Close()
|
|
client := dockerClient{baseURL: validDocker.URL, client: validDocker.Client()}
|
|
if err := client.ensureInternalNetwork("creatorhub_browser"); err != nil {
|
|
t.Fatalf("expected owned isolated network to be accepted: %v", err)
|
|
}
|
|
|
|
tests := map[string]func(map[string]any){
|
|
"wrong name": func(network map[string]any) { network["Name"] = "other" },
|
|
"wrong driver": func(network map[string]any) { network["Driver"] = "overlay" },
|
|
"external": func(network map[string]any) { network["Internal"] = false },
|
|
"attachable": func(network map[string]any) { network["Attachable"] = true },
|
|
"ingress": func(network map[string]any) { network["Ingress"] = true },
|
|
"missing ownership": func(network map[string]any) {
|
|
network["Labels"] = map[string]string{networkRoleLabel: browserNetworkRole}
|
|
},
|
|
"wrong role": func(network map[string]any) {
|
|
network["Labels"] = map[string]string{managedLabel: "true", networkRoleLabel: "control"}
|
|
},
|
|
"missing ICC": func(network map[string]any) { network["Options"] = map[string]string{} },
|
|
"enabled ICC": func(network map[string]any) { network["Options"] = map[string]string{bridgeICCOption: "true"} },
|
|
"extra option": func(network map[string]any) {
|
|
network["Options"] = map[string]string{bridgeICCOption: "false", "unexpected": "value"}
|
|
},
|
|
}
|
|
|
|
for name, mutate := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
network := make(map[string]any, len(valid))
|
|
for key, value := range valid {
|
|
network[key] = value
|
|
}
|
|
mutate(network)
|
|
docker := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) {
|
|
_ = json.NewEncoder(response).Encode(network)
|
|
}))
|
|
defer docker.Close()
|
|
|
|
client := dockerClient{baseURL: docker.URL, client: docker.Client()}
|
|
if err := client.ensureInternalNetwork("creatorhub_browser"); err == nil {
|
|
t.Fatal("expected unsafe existing network to be rejected")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEnsureInternalNetworkRejectsControlNetwork(t *testing.T) {
|
|
requested := false
|
|
docker := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { requested = true }))
|
|
defer docker.Close()
|
|
|
|
client := dockerClient{baseURL: docker.URL, client: docker.Client()}
|
|
if err := client.ensureInternalNetwork(controlNetworkName); err == nil || requested {
|
|
t.Fatalf("expected control network to be rejected before Docker request, requested=%v err=%v", requested, err)
|
|
}
|
|
}
|