diff --git a/ee/debug/checkups/init_logs_darwin.go b/ee/debug/checkups/init_logs_darwin.go index 12662a10d..79df56cfa 100644 --- a/ee/debug/checkups/init_logs_darwin.go +++ b/ee/debug/checkups/init_logs_darwin.go @@ -4,8 +4,6 @@ import ( "archive/zip" "context" "fmt" - "io" - "os" "path/filepath" ) @@ -17,20 +15,9 @@ func writeInitLogs(_ context.Context, logZip *zip.Writer) error { var lastErr error for _, f := range stdMatches { - out, err := logZip.Create(filepath.Base(f)) - if err != nil { + if err := addFileToZip(logZip, f); err != nil { lastErr = err - continue } - - in, err := os.Open(f) - if err != nil { - lastErr = err - continue - } - defer in.Close() - - io.Copy(out, in) } return lastErr diff --git a/ee/debug/checkups/init_logs_linux.go b/ee/debug/checkups/init_logs_linux.go index 8efeb249e..065ccc072 100644 --- a/ee/debug/checkups/init_logs_linux.go +++ b/ee/debug/checkups/init_logs_linux.go @@ -2,8 +2,10 @@ package checkups import ( "archive/zip" + "bytes" "context" "fmt" + "time" "github.com/kolide/launcher/ee/allowedcmd" ) @@ -14,13 +16,10 @@ func writeInitLogs(ctx context.Context, logZip *zip.Writer) error { return fmt.Errorf("creating journalctl command: %w", err) } - outFile, err := logZip.Create("linux_journalctl_launcher_logs.json") + output, err := cmd.Output() if err != nil { return fmt.Errorf("creating linux_journalctl_launcher_logs.json: %w", err) } - cmd.Stderr = outFile - cmd.Stdout = outFile - - return cmd.Run() + return addStreamToZip(logZip, "linux_journalctl_launcher_logs.json", time.Now(), bytes.NewReader(output)) } diff --git a/ee/debug/checkups/init_logs_windows.go b/ee/debug/checkups/init_logs_windows.go index a62ca7c1b..bd967d11e 100644 --- a/ee/debug/checkups/init_logs_windows.go +++ b/ee/debug/checkups/init_logs_windows.go @@ -2,8 +2,10 @@ package checkups import ( "archive/zip" + "bytes" "context" "fmt" + "time" "github.com/kolide/launcher/ee/allowedcmd" ) @@ -15,13 +17,11 @@ func writeInitLogs(ctx context.Context, logZip *zip.Writer) error { return fmt.Errorf("creating powershell command: %w", err) } - outFile, err := logZip.Create("windows_launcher_events.json") + // Capture command output + output, err := cmd.Output() if err != nil { return fmt.Errorf("creating windows_launcher_events.json: %w", err) } - cmd.Stderr = outFile - cmd.Stdout = outFile - - return cmd.Run() + return addStreamToZip(logZip, "windows_launcher_events.json", time.Now(), bytes.NewReader(output)) } diff --git a/ee/debug/checkups/install.go b/ee/debug/checkups/install.go index a6b99b820..3c5ed90bd 100644 --- a/ee/debug/checkups/install.go +++ b/ee/debug/checkups/install.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "io" - "os" "runtime" "github.com/kolide/launcher/pkg/launcher" @@ -55,52 +54,16 @@ func gatherInstallationLogs(z *zip.Writer) error { return nil } - out, err := z.Create("macos-var-log-install.log") - if err != nil { - return fmt.Errorf("creating macos-var-log-install.log in zip: %w", err) - } - - installLog, err := os.Open("/var/log/install.log") - if err != nil { - return fmt.Errorf("opening /var/log/install.log: %w", err) - } - defer installLog.Close() - - if _, err := io.Copy(out, installLog); err != nil { - return fmt.Errorf("writing /var/log/install.log contents to zip: %w", err) - } - - return nil + return addFileToZip(z, "/var/log/install.log") } func gatherInstallerInfo(z *zip.Writer) error { if runtime.GOOS == "windows" { - // Installer info is not available on Windows return nil } configDir := launcher.DefaultPath(launcher.EtcDirectory) installerInfoPath := fmt.Sprintf("%s/installer-info.json", configDir) - installerInfoFile, err := os.Open(installerInfoPath) - if err != nil { - // If the file doesn't exist, you might want to skip without error - if os.IsNotExist(err) { - return nil - } - return fmt.Errorf("opening %s: %w", installerInfoPath, err) - } - defer installerInfoFile.Close() - - installerInfoZipPath := "installer-info.json" - out, err := z.Create(installerInfoZipPath) - if err != nil { - return fmt.Errorf("creating %s in zip: %w", installerInfoZipPath, err) - } - - if _, err := io.Copy(out, installerInfoFile); err != nil { - return fmt.Errorf("writing %s contents to zip: %w", installerInfoZipPath, err) - } - - return nil + return addFileToZip(z, installerInfoPath) } diff --git a/ee/debug/checkups/launchd_darwin.go b/ee/debug/checkups/launchd_darwin.go index f99db85bd..a20c0e619 100644 --- a/ee/debug/checkups/launchd_darwin.go +++ b/ee/debug/checkups/launchd_darwin.go @@ -10,8 +10,8 @@ import ( "fmt" "io" "os" - "path/filepath" "strings" + "time" "github.com/kolide/launcher/ee/allowedcmd" ) @@ -31,38 +31,24 @@ func (c *launchdCheckup) Name() string { } func (c *launchdCheckup) Run(ctx context.Context, extraWriter io.Writer) error { - // Check that the plist exists (this uses open not stat, because we also want to copy it) - launchdPlist, err := os.Open(launchdPlistPath) - if os.IsNotExist(err) { + // Check that the plist exists + if _, err := os.Stat(launchdPlistPath); os.IsNotExist(err) { c.status = Failing c.summary = "plist does not exist" return nil - } else if err != nil { - c.status = Failing - c.summary = fmt.Sprintf("error reading %s: %s", launchdPlistPath, err) - return nil } - defer launchdPlist.Close() extraZip := zip.NewWriter(extraWriter) defer extraZip.Close() - zippedPlist, err := extraZip.Create(filepath.Base(launchdPlistPath)) - if err != nil { - c.status = Erroring - c.summary = fmt.Sprintf("unable to create extra information: %s", err) - return nil - } - - if _, err := io.Copy(zippedPlist, launchdPlist); err != nil { + // Add plist file using our utility + if err := addFileToZip(extraZip, launchdPlistPath); err != nil { c.status = Erroring - c.summary = fmt.Sprintf("unable to write extra information: %s", err) + c.summary = fmt.Sprintf("unable to add plist file: %s", err) return nil } - // run launchctl to check status - var printOut bytes.Buffer - + // Run launchctl to check status cmd, err := allowedcmd.Launchctl(ctx, "print", launchdServiceName) if err != nil { c.status = Erroring @@ -70,28 +56,21 @@ func (c *launchdCheckup) Run(ctx context.Context, extraWriter io.Writer) error { return nil } - cmd.Stdout = &printOut - cmd.Stderr = &printOut - if err := cmd.Run(); err != nil { + output, err := cmd.Output() + if err != nil { c.status = Failing c.summary = fmt.Sprintf("error running launchctl print: %s", err) return nil } - zippedOut, err := extraZip.Create("launchctl-print.txt") - if err != nil { + // Add command output using our streaming utility + if err := addStreamToZip(extraZip, "launchctl-print.txt", time.Now(), bytes.NewReader(output)); err != nil { c.status = Erroring - c.summary = fmt.Sprintf("unable to create launchctl-print.txt: %s", err) + c.summary = fmt.Sprintf("unable to add launchctl-print.txt output: %s", err) return nil } - if _, err := zippedOut.Write(printOut.Bytes()); err != nil { - c.status = Erroring - c.summary = fmt.Sprintf("unable to write launchctl-print.txt: %s", err) - return nil - - } - if !strings.Contains(printOut.String(), "state = running") { + if !strings.Contains(string(output), "state = running") { c.status = Failing c.summary = "state not active" return nil diff --git a/ee/debug/checkups/logs.go b/ee/debug/checkups/logs.go index 80bd81613..817e5339b 100644 --- a/ee/debug/checkups/logs.go +++ b/ee/debug/checkups/logs.go @@ -48,22 +48,13 @@ func (c *Logs) Run(_ context.Context, fullFH io.Writer) error { matches, _ := filepath.Glob(filepath.Join(c.k.RootDirectory(), "debug*")) for _, f := range matches { - out, err := logZip.Create(filepath.Base(f)) - if err != nil { - continue + if err := addFileToZip(logZip, f); err != nil { + return fmt.Errorf("adding %s to zip: %w", f, err) } - - in, err := os.Open(f) - if err != nil { - fmt.Fprintf(out, "error reading file:\n%s", err) - continue - } - defer in.Close() - - io.Copy(out, in) } return nil + } func (c *Logs) Status() Status { diff --git a/ee/debug/checkups/power_windows.go b/ee/debug/checkups/power_windows.go index 3b65e8f9e..6fd2eda86 100644 --- a/ee/debug/checkups/power_windows.go +++ b/ee/debug/checkups/power_windows.go @@ -5,10 +5,12 @@ package checkups import ( "archive/zip" + "bytes" "context" "fmt" "io" "os" + "time" "github.com/kolide/launcher/ee/agent" "github.com/kolide/launcher/ee/allowedcmd" @@ -35,24 +37,15 @@ func (p *powerCheckup) Run(ctx context.Context, extraWriter io.Writer) error { return fmt.Errorf("running powercfg.exe: error %w, output %s", err, string(out)) } - sprHandle, err := os.Open(tmpFilePath) - if err != nil { - return fmt.Errorf("opening system power report: %w", err) - } - defer sprHandle.Close() - extraZip := zip.NewWriter(extraWriter) defer extraZip.Close() - zippedPowerReport, err := extraZip.Create("power.html") - if err != nil { - return fmt.Errorf("creating power report zip file: %w", err) - } - - if _, err := io.Copy(zippedPowerReport, sprHandle); err != nil { - return fmt.Errorf("copying system power report: %w", err) + // Add the power report using addFileToZip + if err := addFileToZip(extraZip, tmpFilePath); err != nil { + return fmt.Errorf("adding power report to zip: %w", err) } + // Get available sleep states powerCfgSleepStatesCmd, err := allowedcmd.Powercfg(ctx, "/availablesleepstates") if err != nil { return fmt.Errorf("creating powercfg sleep states command: %w", err) @@ -61,36 +54,29 @@ func (p *powerCheckup) Run(ctx context.Context, extraWriter io.Writer) error { hideWindow(powerCfgSleepStatesCmd) availableSleepStatesOutput, err := powerCfgSleepStatesCmd.CombinedOutput() if err != nil { - return fmt.Errorf("running powercfg.exe for sleep states: error %w, output %s", err, string(availableSleepStatesOutput)) - } - - zippedSleepStates, err := extraZip.Create("available_sleep_states.txt") - if err != nil { - return fmt.Errorf("creating available sleep states output file: %w", err) + return fmt.Errorf("running powercfg.exe for sleep states: error %w", err) } - if _, err := zippedSleepStates.Write(availableSleepStatesOutput); err != nil { - return fmt.Errorf("writing available sleep states output file: %w", err) + // Add sleep states using addStreamToZip + if err := addStreamToZip(extraZip, "available_sleep_states.txt", time.Now(), bytes.NewReader(availableSleepStatesOutput)); err != nil { + return fmt.Errorf("running powercfg.exe for sleep states: error %w, output %s", err, string(availableSleepStatesOutput)) } + // Get power events eventFilter := `Get-Winevent -FilterHashtable @{LogName='System'; ProviderName='Microsoft-Windows-Power-Troubleshooter','Microsoft-Windows-Kernel-Power'} -MaxEvents 500 | Select-Object @{name='Time'; expression={$_.TimeCreated.ToString("O")}},Id,LogName,ProviderName,Message,TimeCreated | ConvertTo-Json` getWinEventCmd, err := allowedcmd.Powershell(ctx, eventFilter) if err != nil { return fmt.Errorf("creating powershell get-winevent command: %w", err) } - powerEventFile, err := extraZip.Create("windows_power_events.json") - if err != nil { - return fmt.Errorf("creating windows_power_events.json: %w", err) - } - - powerEventsOut, err := getWinEventCmd.CombinedOutput() + powerEventsOutput, err := getWinEventCmd.Output() if err != nil { - return fmt.Errorf("running get-winevent command: %w, output %s", err, string(powerEventsOut)) + return fmt.Errorf("running get-winevent command: %w, output %s", err, string(powerEventsOutput)) } - if _, err := powerEventFile.Write(powerEventsOut); err != nil { - return fmt.Errorf("writing power events to output file: %w", err) + // Add power events using addStreamToZip + if err := addStreamToZip(extraZip, "windows_power_events.json", time.Now(), bytes.NewReader(powerEventsOutput)); err != nil { + return fmt.Errorf("adding power events to zip: %w", err) } return nil diff --git a/ee/debug/checkups/util.go b/ee/debug/checkups/util.go index 02c7b7681..edb2df120 100644 --- a/ee/debug/checkups/util.go +++ b/ee/debug/checkups/util.go @@ -52,18 +52,20 @@ type fileInfo struct { // addFileToZip takes a file path, and a zip writer, and adds the file and some metadata. func addFileToZip(z *zip.Writer, location string) error { + // Create metadata file first, keeping existing pattern metaout, err := z.Create(filepath.Join(".", location+".flaremeta")) if err != nil { return fmt.Errorf("creating %s in zip: %w", location+".flaremeta", err) } - // Not totally clear if we should use Lstat or Stat here. + // Get file info fi, err := os.Stat(location) if os.IsNotExist(err) || os.IsPermission(err) { fmt.Fprintf(metaout, `{ "error stating file": "%s" }`, err) return nil } + // Marshal metadata b, err := json.Marshal(fileInfo{ Name: fi.Name(), Size: fi.Size(), @@ -80,9 +82,11 @@ func addFileToZip(z *zip.Writer, location string) error { if err := json.Indent(&buf, b, "", " "); err != nil { // Structural error. Abort return fmt.Errorf("indenting json: %w", err) + } + if _, err := metaout.Write(buf.Bytes()); err != nil { + return fmt.Errorf("writing metadata: %w", err) } - metaout.Write(buf.Bytes()) // // Done with metadata, and we know the file exists, and that we have permission to it. @@ -95,7 +99,15 @@ func addFileToZip(z *zip.Writer, location string) error { } defer fh.Close() - dataout, err := z.Create(filepath.Join(".", location)) + // Create zip header with metadata + header, err := zip.FileInfoHeader(fi) + if err != nil { + return fmt.Errorf("creating file header: %w", err) + } + header.Name = filepath.Join(".", location) + + // Create file in zip with metadata + dataout, err := z.CreateHeader(header) if err != nil { return fmt.Errorf("creating %s in zip: %w", location, err) } @@ -107,6 +119,50 @@ func addFileToZip(z *zip.Writer, location string) error { return nil } +func addStreamToZip(z *zip.Writer, name string, modTime time.Time, reader io.Reader) error { + // Create metadata file first + metaout, err := z.Create(name + ".flaremeta") + if err != nil { + return fmt.Errorf("creating %s in zip: %w", name+".flaremeta", err) + } + + // Marshal metadata + b, err := json.Marshal(fileInfo{ + Name: filepath.Base(name), + ModTime: modTime, + }) + if err != nil { + return fmt.Errorf("marshalling json: %w", err) + } + + var buf bytes.Buffer + if err := json.Indent(&buf, b, "", " "); err != nil { + return fmt.Errorf("indenting json: %w", err) + } + + if _, err := metaout.Write(buf.Bytes()); err != nil { + return fmt.Errorf("writing metadata: %w", err) + } + + // Create the main file in zip + header := &zip.FileHeader{ + Name: name, + Method: zip.Deflate, + Modified: modTime, + } + + out, err := z.CreateHeader(header) + if err != nil { + return fmt.Errorf("creating %s in zip: %w", name, err) + } + + if _, err := io.Copy(out, reader); err != nil { + return fmt.Errorf("copying data to zip: %w", err) + } + + return nil +} + var ignoredEnvPrefixes = []string{ "LESS", "LS_COLORS",