GetAPIKeyFromHelper executes a shell command to dynamically generate an API key. The command is executed in /bin/sh with a timeout controlled by the provided context. It returns the trimmed output from stdout, or an error if the command fails. On timeout, it kills the entire process group (shell an
(ctx context.Context, helperCmd string)
| 22 | // |
| 23 | // Security note: The returned API key is sensitive and should not be logged. |
| 24 | func GetAPIKeyFromHelper(ctx context.Context, helperCmd string) (string, error) { |
| 25 | if helperCmd == "" { |
| 26 | return "", errors.New("api_key_helper command is empty") |
| 27 | } |
| 28 | |
| 29 | // Create context with timeout if not already set |
| 30 | if _, hasDeadline := ctx.Deadline(); !hasDeadline { |
| 31 | var cancel context.CancelFunc |
| 32 | ctx, cancel = context.WithTimeout(ctx, HelperTimeout) |
| 33 | defer cancel() |
| 34 | } |
| 35 | |
| 36 | // Execute command in /bin/sh |
| 37 | cmd := exec.CommandContext(ctx, "/bin/sh", "-c", helperCmd) |
| 38 | |
| 39 | // Create a new process group so we can kill all descendants on timeout |
| 40 | cmd.SysProcAttr = &syscall.SysProcAttr{ |
| 41 | Setpgid: true, |
| 42 | } |
| 43 | |
| 44 | var stdout, stderr bytes.Buffer |
| 45 | cmd.Stdout = &stdout |
| 46 | cmd.Stderr = &stderr |
| 47 | |
| 48 | // Start the command |
| 49 | if err := cmd.Start(); err != nil { |
| 50 | return "", fmt.Errorf("api_key_helper start failed: %w", err) |
| 51 | } |
| 52 | |
| 53 | // Wait for command completion in a goroutine |
| 54 | done := make(chan error, 1) |
| 55 | go func() { |
| 56 | // Always Wait to avoid zombie processes |
| 57 | done <- cmd.Wait() |
| 58 | }() |
| 59 | |
| 60 | select { |
| 61 | case err := <-done: |
| 62 | // Command completed normally |
| 63 | if err != nil { |
| 64 | // Don't include stderr in error message as it might contain sensitive info |
| 65 | return "", fmt.Errorf("api_key_helper command failed: %w", err) |
| 66 | } |
| 67 | apiKey := strings.TrimSpace(stdout.String()) |
| 68 | if apiKey == "" { |
| 69 | return "", errors.New("api_key_helper command returned empty output") |
| 70 | } |
| 71 | return apiKey, nil |
| 72 | |
| 73 | case <-ctx.Done(): |
| 74 | // Timeout or cancellation: terminate the process group gracefully, then forcefully |
| 75 | if cmd.Process == nil { |
| 76 | // Process handle not initialized; wait for cleanup and report timeout |
| 77 | <-done |
| 78 | return "", fmt.Errorf("api_key_helper command timeout after %v", HelperTimeout) |
| 79 | } |
| 80 | pgid := cmd.Process.Pid |
| 81 |