-
Notifications
You must be signed in to change notification settings - Fork 5
feat: add signature agent #71
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
Changes from 2 commits
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 |
|---|---|---|
|
|
@@ -435,7 +435,7 @@ var extensionsBuildWebBotAuthCmd = &cobra.Command{ | |
| url, _ := cmd.Flags().GetString("url") | ||
| keyPath, _ := cmd.Flags().GetString("key") | ||
| uploadName, _ := cmd.Flags().GetString("upload") | ||
|
|
||
| signatureAgentURL, _ := cmd.Flags().GetString("signature-agent") | ||
| // Use upload name for extension name, or default to "web-bot-auth" | ||
| extensionName := "web-bot-auth" | ||
| if uploadName != "" { | ||
|
|
@@ -444,11 +444,12 @@ var extensionsBuildWebBotAuthCmd = &cobra.Command{ | |
|
|
||
| // Build the extension | ||
| result, err := extensions.BuildWebBotAuth(cmd.Context(), extensions.ExtensionsBuildWebBotAuthInput{ | ||
| Output: output, | ||
| HostURL: url, | ||
| KeyPath: keyPath, | ||
| ExtensionName: extensionName, | ||
| AutoUpload: uploadName != "", | ||
| Output: output, | ||
| HostURL: url, | ||
| KeyPath: keyPath, | ||
| ExtensionName: extensionName, | ||
| AutoUpload: uploadName != "", | ||
| SignatureAgentURL: signatureAgentURL, | ||
| }) | ||
| if err != nil { | ||
| return err | ||
|
|
@@ -489,4 +490,5 @@ func init() { | |
| extensionsBuildWebBotAuthCmd.Flags().String("url", "http://127.0.0.1:10001", "Base URL for update.xml and policy templates") | ||
| extensionsBuildWebBotAuthCmd.Flags().String("key", "", "Path to Ed25519 private key file (JWK or PEM format)") | ||
| extensionsBuildWebBotAuthCmd.Flags().String("upload", "", "Upload extension to Kernel with specified name (e.g., --upload web-bot-auth)") | ||
| extensionsBuildWebBotAuthCmd.Flags().String("signature-agent", "", "URL of the signature agent") | ||
|
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. nit: the description is vague. consider something like: "Base URL of the signature agent (e.g., https://agent.example.com). Verifiers will look up /.well-known/http-message-signatures-directory at this URL." |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,24 +18,23 @@ import ( | |
| ) | ||
|
|
||
| const ( | ||
| defaultLocalhostURL = "http://localhost:8000" | ||
| defaultDirMode = 0755 | ||
| defaultFileMode = 0644 | ||
| // Current: v0.6.0 release (e3d76846b64be03ae00e2b9e53b697beab81541d) - Dec 19, 2025 | ||
| webBotAuthCommit = "e3d76846b64be03ae00e2b9e53b697beab81541d" | ||
| webBotAuthDownloadURL = "https://github.com/cloudflare/web-bot-auth/archive/" + webBotAuthCommit + ".zip" | ||
| defaultLocalhostURL = "http://localhost:8000" | ||
| defaultDirMode = 0755 | ||
| defaultFileMode = 0644 | ||
| webBotAuthDownloadURL = "https://github.com/kernel/web-bot-auth/archive/refs/heads/main.zip" | ||
cursor[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| downloadTimeout = 5 * time.Minute | ||
| // defaultWebBotAuthKey is the RFC9421 test key that works with Cloudflare's test site | ||
| // https://developers.cloudflare.com/bots/reference/bot-verification/web-bot-auth/ | ||
| defaultWebBotAuthKey = `{"kty":"OKP","crv":"Ed25519","d":"n4Ni-HpISpVObnQMW0wOhCKROaIKqKtW_2ZYb2p9KcU","x":"JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs"}` | ||
| ) | ||
|
|
||
| type ExtensionsBuildWebBotAuthInput struct { | ||
| Output string | ||
| HostURL string | ||
| KeyPath string // Path to user's JWK or PEM file (optional, defaults to RFC9421 test key) | ||
| ExtensionName string // Name for the extension paths (defaults to "web-bot-auth") | ||
| AutoUpload bool // Whether the extension will be automatically uploaded after building | ||
| Output string | ||
| HostURL string | ||
| KeyPath string // Path to user's JWK or PEM file (optional, defaults to RFC9421 test key) | ||
| ExtensionName string // Name for the extension paths (defaults to "web-bot-auth") | ||
| AutoUpload bool // Whether the extension will be automatically uploaded after building | ||
| SignatureAgentURL string // URL of the signature agent | ||
| } | ||
|
|
||
| // BuildWebBotAuthOutput contains the result of building the extension | ||
|
|
@@ -100,7 +99,7 @@ func BuildWebBotAuth(ctx context.Context, in ExtensionsBuildWebBotAuthInput) (*B | |
| } | ||
|
|
||
| // Build extension | ||
| extensionID, err := buildWebBotAuthExtension(ctx, browserExtDir, in.HostURL, keyData, in.ExtensionName) | ||
| extensionID, err := buildWebBotAuthExtension(ctx, browserExtDir, in.HostURL, keyData, in.ExtensionName, in.SignatureAgentURL) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
@@ -219,7 +218,7 @@ func downloadAndExtractWebBotAuth(ctx context.Context) (browserExtDir string, cl | |
|
|
||
| // buildWebBotAuthExtension modifies templates, builds the extension, and returns the extension ID | ||
| // extensionName is used for URL paths (e.g., "web-bot-auth") instead of the Chrome extension ID | ||
| func buildWebBotAuthExtension(ctx context.Context, browserExtDir, hostURL, keyData, extensionName string) (string, error) { | ||
| func buildWebBotAuthExtension(ctx context.Context, browserExtDir, hostURL, keyData, extensionName, signatureAgentURL string) (string, error) { | ||
| // Normalize hostURL by removing trailing slashes to prevent double slashes in URLs | ||
| hostURL = strings.TrimRight(hostURL, "/") | ||
|
|
||
|
|
@@ -248,6 +247,14 @@ func buildWebBotAuthExtension(ctx context.Context, browserExtDir, hostURL, keyDa | |
| } | ||
| pterm.Success.Println("Private key written successfully") | ||
|
|
||
| // Inject the JWK into background.ts (replacing the hardcoded test key) | ||
| pterm.Info.Println("Injecting custom JWK into background.ts...") | ||
| backgroundTsPath := filepath.Join(browserExtDir, "src", "background.ts") | ||
| if err := injectJWKIntoBackgroundTs(backgroundTsPath, keyData); err != nil { | ||
| return "", fmt.Errorf("failed to inject JWK: %w", err) | ||
| } | ||
| pterm.Success.Println("Custom JWK injected successfully") | ||
archandatta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Modify template files | ||
| pterm.Info.Println("Modifying templates with host URL...") | ||
|
|
||
|
|
@@ -293,6 +300,7 @@ func buildWebBotAuthExtension(ctx context.Context, browserExtDir, hostURL, keyDa | |
| pterm.Info.Println("Building extension...") | ||
| npmBuild := exec.CommandContext(ctx, "npm", "run", "build:chrome") | ||
| npmBuild.Dir = browserExtDir | ||
| npmBuild.Env = append(os.Environ(), "SIGNATURE_AGENT_URL="+signatureAgentURL) | ||
| npmBuild.Stdout = os.Stdout | ||
| npmBuild.Stderr = os.Stderr | ||
| if err := npmBuild.Run(); err != nil { | ||
|
|
@@ -303,6 +311,7 @@ func buildWebBotAuthExtension(ctx context.Context, browserExtDir, hostURL, keyDa | |
| pterm.Info.Println("Bundling extension...") | ||
| npmBundle := exec.CommandContext(ctx, "npm", "run", "bundle:chrome") | ||
| npmBundle.Dir = browserExtDir | ||
| npmBundle.Env = append(os.Environ(), "SIGNATURE_AGENT_URL="+signatureAgentURL) | ||
| var bundleOutput bytes.Buffer | ||
| npmBundle.Stdout = io.MultiWriter(os.Stdout, &bundleOutput) | ||
| npmBundle.Stderr = os.Stderr | ||
|
|
@@ -347,6 +356,34 @@ func buildWebBotAuthExtension(ctx context.Context, browserExtDir, hostURL, keyDa | |
| return extensionID, nil | ||
| } | ||
|
|
||
| // injectJWKIntoBackgroundTs replaces the hardcoded test key import with the custom JWK | ||
| func injectJWKIntoBackgroundTs(backgroundTsPath, jwkData string) error { | ||
| content, err := os.ReadFile(backgroundTsPath) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to read background.ts: %w", err) | ||
| } | ||
|
|
||
| contentStr := string(content) | ||
|
|
||
| // Replace the import line with an inline constant | ||
| // Find: import jwk from "../../rfc9421-keys/ed25519.json" assert { type: "json" }; | ||
| // Replace with: const jwk = {your-jwk-here}; | ||
| searchPattern := `import jwk from "../../rfc9421-keys/ed25519.json" assert { type: "json" };` | ||
| replacement := fmt.Sprintf("const jwk = %s;", jwkData) | ||
|
|
||
| if !strings.Contains(contentStr, searchPattern) { | ||
| return fmt.Errorf("could not find JWK import statement in background.ts") | ||
| } | ||
|
|
||
| contentStr = strings.Replace(contentStr, searchPattern, replacement, 1) | ||
|
Comment on lines
+377
to
+387
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 part seems kinda flakey. We are basically writing typescript code from Go to make a one line replacement. |
||
|
|
||
| if err := os.WriteFile(backgroundTsPath, []byte(contentStr), 0644); err != nil { | ||
| return fmt.Errorf("failed to write modified background.ts: %w", err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // copyExtensionArtifacts copies built extension files to the output directory | ||
| func copyExtensionArtifacts(browserExtDir, outputDir string) error { | ||
| pterm.Info.Println("Copying extension files to output directory...") | ||
|
|
||
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.
perhaps we should add that kernel is explicitly add to the helper that Kernel is expecting a ed25519 key. This reason being that ConvertPEMToJWK validates that
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.
That is mentioned in the
--keyflag