Skip to content

Commit 1efa28e

Browse files
micahleeGitHub Enterprise
authored andcommitted
Merge pull request #17 from Conjur-Enterprise/cnjr-11976-host-command-history
CNJR-11976: Add host command history to inspect report
2 parents 66b7cc6 + c3a5efc commit 1efa28e

File tree

7 files changed

+719
-4
lines changed

7 files changed

+719
-4
lines changed

.devops/gitleaks.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
config:
2+
exclusions:
3+
# These tests include mock secrets to test the command history sanitizing
4+
- "pkg/checks/command_history_test.go"
5+
- "pkg/checks/sanitize/sanitizer_test.go"

CHANGELOG.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
66

77
## [Unreleased]
88

9-
### Added
10-
- Runit services status check that records service statuses to the inspect
11-
report. CNJR-11977
12-
139
### Changed
1410
- Nothing should go in this section, please add to the latest unreleased version
1511
(and update the corresponding date), or add a new version.
1612

13+
## [0.5.0] - 2025-12-04
14+
15+
### Added
16+
- Runit services status check that records service statuses to the inspect
17+
report. CNJR-11977
18+
- Command history check that records the recent command history from the host
19+
machine to the inspect report. CNJR-11976
20+
1721
## [0.4.2] - 2025-03-25
1822

1923
### Added

pkg/checks/command_history.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Package checks defines all of the possible Conjur Inspect checks that can
2+
// be run.
3+
package checks
4+
5+
import (
6+
"bufio"
7+
"fmt"
8+
"os"
9+
"path/filepath"
10+
"strings"
11+
12+
"github.com/cyberark/conjur-inspect/pkg/check"
13+
"github.com/cyberark/conjur-inspect/pkg/checks/sanitize"
14+
"github.com/cyberark/conjur-inspect/pkg/log"
15+
)
16+
17+
// userHomeDirFunc is a mockable version of os.UserHomeDir for testing
18+
var userHomeDirFunc = os.UserHomeDir
19+
20+
// CommandHistory collects recent command history from the host machine
21+
type CommandHistory struct{}
22+
23+
// Describe provides a textual description of what this check gathers info on
24+
func (*CommandHistory) Describe() string {
25+
return "Command History"
26+
}
27+
28+
// Run performs the command history collection
29+
func (ch *CommandHistory) Run(runContext *check.RunContext) []check.Result {
30+
homeDir, err := userHomeDirFunc()
31+
if err != nil {
32+
log.Warn("failed to determine home directory: %s", err)
33+
return []check.Result{{
34+
Title: "Command History",
35+
Status: check.StatusInfo,
36+
Message: "Unable to determine home directory",
37+
}}
38+
}
39+
40+
// Try to read history files in order of preference
41+
historyPaths := []string{
42+
filepath.Join(homeDir, ".zsh_history"),
43+
filepath.Join(homeDir, ".bash_history"),
44+
}
45+
46+
var historyContent strings.Builder
47+
historyFound := false
48+
49+
for _, historyPath := range historyPaths {
50+
content, err := readAndLimitHistoryFile(historyPath, 100)
51+
if err == nil && len(content) > 0 {
52+
historyContent.WriteString(content)
53+
historyFound = true
54+
break
55+
}
56+
if err != nil && !os.IsNotExist(err) {
57+
log.Warn("error reading history file %s: %s", historyPath, err)
58+
}
59+
}
60+
61+
// If no history files were found or readable, return INFO result
62+
if !historyFound {
63+
return []check.Result{{
64+
Title: "Command History",
65+
Status: check.StatusInfo,
66+
Message: "No command history files found or accessible",
67+
}}
68+
}
69+
70+
// Sanitize the history to redact sensitive values
71+
redactor := sanitize.NewRedactor()
72+
sanitizedContent := redactor.RedactLines(historyContent.String())
73+
74+
// Save history to output store
75+
_, err = runContext.OutputStore.Save(
76+
"host-command-history.txt",
77+
strings.NewReader(sanitizedContent),
78+
)
79+
if err != nil {
80+
log.Warn("failed to save command history output: %s", err)
81+
}
82+
83+
// Return empty results on success (output is saved)
84+
return []check.Result{}
85+
}
86+
87+
// readAndLimitHistoryFile reads a history file and returns the last N lines
88+
func readAndLimitHistoryFile(filePath string, maxLines int) (string, error) {
89+
file, err := os.Open(filePath)
90+
if err != nil {
91+
return "", err
92+
}
93+
defer file.Close()
94+
95+
var lines []string
96+
scanner := bufio.NewScanner(file)
97+
98+
for scanner.Scan() {
99+
lines = append(lines, scanner.Text())
100+
}
101+
102+
if err := scanner.Err(); err != nil {
103+
return "", fmt.Errorf("error scanning file: %w", err)
104+
}
105+
106+
// Return the last maxLines lines
107+
startIndex := 0
108+
if len(lines) > maxLines {
109+
startIndex = len(lines) - maxLines
110+
}
111+
112+
return strings.Join(lines[startIndex:], "\n"), nil
113+
}

0 commit comments

Comments
 (0)