-
Notifications
You must be signed in to change notification settings - Fork 35
[kernel 726] fix web bot auth extension #110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7a4f031
905d8d9
f9e1cee
fe4f952
e52fc3f
058a6aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -124,6 +124,64 @@ func main() { | |
| apiService.HandleProcessAttach(w, r, id) | ||
| }) | ||
|
|
||
| // Serve extension files for Chrome policy-installed extensions | ||
| // This allows Chrome to download .crx and update.xml files via HTTP | ||
| extensionsDir := "/home/kernel/extensions" | ||
| r.Get("/extensions/*", func(w http.ResponseWriter, r *http.Request) { | ||
| // Serve files from /home/kernel/extensions/ | ||
| fs := http.StripPrefix("/extensions/", http.FileServer(http.Dir(extensionsDir))) | ||
| fs.ServeHTTP(w, r) | ||
| }) | ||
|
|
||
| // Serve update.xml at root for Chrome enterprise policy | ||
| // This serves the first update.xml found in any extension directory | ||
| r.Get("/update.xml", func(w http.ResponseWriter, r *http.Request) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this serves the first |
||
| // Try to find update.xml in the first extension directory | ||
| entries, err := os.ReadDir(extensionsDir) | ||
| if err != nil { | ||
| http.Error(w, "extensions directory not found", http.StatusNotFound) | ||
| return | ||
| } | ||
|
|
||
| for _, entry := range entries { | ||
| if entry.IsDir() { | ||
| updateXMLPath := fmt.Sprintf("%s/%s/update.xml", extensionsDir, entry.Name()) | ||
| if _, err := os.Stat(updateXMLPath); err == nil { | ||
| http.ServeFile(w, r, updateXMLPath) | ||
| return | ||
| } | ||
| } | ||
| } | ||
|
|
||
| http.Error(w, "update.xml not found", http.StatusNotFound) | ||
| }) | ||
|
|
||
| // Serve CRX files at root for Chrome enterprise policy | ||
| // This allows simple codebase URLs like http://host:port/extension-name.crx | ||
| r.Get("/{filename}.crx", func(w http.ResponseWriter, r *http.Request) { | ||
| // Extract the filename from the URL path | ||
| filename := chi.URLParam(r, "filename") + ".crx" | ||
|
|
||
| // Search for the CRX file in all extension directories | ||
| entries, err := os.ReadDir(extensionsDir) | ||
| if err != nil { | ||
| http.Error(w, "extensions directory not found", http.StatusNotFound) | ||
| return | ||
| } | ||
|
|
||
| for _, entry := range entries { | ||
| if entry.IsDir() { | ||
| crxPath := fmt.Sprintf("%s/%s/%s", extensionsDir, entry.Name(), filename) | ||
| if _, err := os.Stat(crxPath); err == nil { | ||
| http.ServeFile(w, r, crxPath) | ||
| return | ||
| } | ||
| } | ||
| } | ||
|
|
||
| http.Error(w, "crx file not found", http.StatusNotFound) | ||
| }) | ||
|
|
||
| srv := &http.Server{ | ||
| Addr: fmt.Sprintf(":%d", config.Port), | ||
| Handler: r, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -804,115 +804,3 @@ func listCDPTargets(ctx context.Context) ([]map[string]interface{}, error) { | |
|
|
||
| return targets, nil | ||
| } | ||
|
|
||
| func TestWebBotAuthInstallation(t *testing.T) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this test was verifying the web-bot-auth policy installation flow. rather than deleting it, could we update it to test the new behavior? the new flow (requiring |
||
| image := headlessImage | ||
| name := containerName + "-web-bot-auth" | ||
|
|
||
| logger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelInfo})) | ||
| baseCtx := logctx.AddToContext(context.Background(), logger) | ||
|
|
||
| if _, err := exec.LookPath("docker"); err != nil { | ||
| require.NoError(t, err, "docker not available: %v", err) | ||
| } | ||
|
|
||
| // Clean slate | ||
| _ = stopContainer(baseCtx, name) | ||
|
|
||
| env := map[string]string{} | ||
|
|
||
| // Start container | ||
| _, exitCh, err := runContainer(baseCtx, image, name, env) | ||
| require.NoError(t, err, "failed to start container: %v", err) | ||
| defer stopContainer(baseCtx, name) | ||
|
|
||
| ctx, cancel := context.WithTimeout(baseCtx, 3*time.Minute) | ||
| defer cancel() | ||
|
|
||
| logger.Info("[setup]", "action", "waiting for API", "url", apiBaseURL+"/spec.yaml") | ||
| require.NoError(t, waitHTTPOrExit(ctx, apiBaseURL+"/spec.yaml", exitCh), "api not ready: %v", err) | ||
|
|
||
| // Build mock web-bot-auth extension zip in-memory | ||
| extDir := t.TempDir() | ||
| manifest := `{ | ||
| "manifest_version": 3, | ||
| "version": "1.0.0", | ||
| "name": "Web Bot Auth Mock", | ||
| "description": "Mock web-bot-auth extension for testing", | ||
| "permissions": [ | ||
| "webRequest", | ||
| "webRequestBlocking" | ||
| ], | ||
| "host_permissions": [ | ||
| "*://*/*" | ||
| ] | ||
| }` | ||
| err = os.WriteFile(filepath.Join(extDir, "manifest.json"), []byte(manifest), 0600) | ||
| require.NoError(t, err, "write manifest: %v", err) | ||
|
|
||
| extZip, err := zipDirToBytes(extDir) | ||
| require.NoError(t, err, "zip ext: %v", err) | ||
|
|
||
| // Upload extension using the API | ||
| { | ||
| client, err := apiClient() | ||
| require.NoError(t, err) | ||
| var body bytes.Buffer | ||
| w := multipart.NewWriter(&body) | ||
| fw, err := w.CreateFormFile("extensions.zip_file", "web-bot-auth.zip") | ||
| require.NoError(t, err) | ||
| _, err = io.Copy(fw, bytes.NewReader(extZip)) | ||
| require.NoError(t, err) | ||
| err = w.WriteField("extensions.name", "web-bot-auth") | ||
| require.NoError(t, err) | ||
| err = w.Close() | ||
| require.NoError(t, err) | ||
|
|
||
| logger.Info("[test]", "action", "uploading web-bot-auth extension") | ||
| start := time.Now() | ||
| rsp, err := client.UploadExtensionsAndRestartWithBodyWithResponse(ctx, w.FormDataContentType(), &body) | ||
| elapsed := time.Since(start) | ||
| require.NoError(t, err, "uploadExtensionsAndRestart request error: %v", err) | ||
| require.Equal(t, http.StatusCreated, rsp.StatusCode(), "unexpected status: %s body=%s", rsp.Status(), string(rsp.Body)) | ||
| logger.Info("[test]", "action", "extension uploaded", "elapsed", elapsed.String()) | ||
| } | ||
|
|
||
| // Verify the policy.json file contains the correct web-bot-auth configuration | ||
| { | ||
| logger.Info("[test]", "action", "reading policy.json") | ||
| policyContent, err := execCombinedOutput(ctx, "cat", []string{"/etc/chromium/policies/managed/policy.json"}) | ||
| require.NoError(t, err, "failed to read policy.json: %v", err) | ||
|
|
||
| logger.Info("[test]", "policy_content", policyContent) | ||
|
|
||
| var policy map[string]interface{} | ||
| err = json.Unmarshal([]byte(policyContent), &policy) | ||
| require.NoError(t, err, "failed to parse policy.json: %v", err) | ||
|
|
||
| // Check ExtensionSettings exists | ||
| extensionSettings, ok := policy["ExtensionSettings"].(map[string]interface{}) | ||
| require.True(t, ok, "ExtensionSettings not found in policy.json") | ||
|
|
||
| // Check web-bot-auth entry exists | ||
| webBotAuth, ok := extensionSettings["web-bot-auth"].(map[string]interface{}) | ||
| require.True(t, ok, "web-bot-auth entry not found in ExtensionSettings") | ||
|
|
||
| // Verify installation_mode is force_installed | ||
| installationMode, ok := webBotAuth["installation_mode"].(string) | ||
| require.True(t, ok, "installation_mode not found in web-bot-auth entry") | ||
| require.Equal(t, "force_installed", installationMode, "expected installation_mode to be force_installed") | ||
|
|
||
| // Verify path | ||
| path, ok := webBotAuth["path"].(string) | ||
| require.True(t, ok, "path not found in web-bot-auth entry") | ||
| require.Equal(t, "/home/kernel/extensions/web-bot-auth", path, "expected path to be /home/kernel/extensions/web-bot-auth") | ||
|
|
||
| // Verify runtime_allowed_hosts | ||
| runtimeAllowedHosts, ok := webBotAuth["runtime_allowed_hosts"].([]interface{}) | ||
| require.True(t, ok, "runtime_allowed_hosts not found in web-bot-auth entry") | ||
| require.Len(t, runtimeAllowedHosts, 1, "expected runtime_allowed_hosts to have 1 entry") | ||
| require.Equal(t, "*://*/*", runtimeAllowedHosts[0].(string), "expected runtime_allowed_hosts to contain *://*/*") | ||
|
|
||
| logger.Info("[test]", "result", "web-bot-auth policy verified successfully") | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
var extractionErr erroris more idiomatic