Skip to content

Commit 8dac72b

Browse files
authored
Merge pull request #13307 from NikhilSharmaWe/auditLog
Write commands to audit log regardless of whether it terminates cleanly
2 parents 198e91f + 75f99c9 commit 8dac72b

File tree

8 files changed

+171
-44
lines changed

8 files changed

+171
-44
lines changed

cmd/minikube/cmd/root.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"path/filepath"
2424
"runtime"
2525
"strings"
26-
"time"
2726

2827
"github.com/spf13/cobra"
2928
"github.com/spf13/pflag"
@@ -84,8 +83,16 @@ var RootCmd = &cobra.Command{
8483
// Execute adds all child commands to the root command sets flags appropriately.
8584
// This is called by main.main(). It only needs to happen once to the rootCmd.
8685
func Execute() {
87-
defer audit.Log(time.Now())
88-
86+
auditID, err := audit.LogCommandStart()
87+
if err != nil {
88+
klog.Errorf("failed to log command start to audit: %v", err)
89+
}
90+
defer func() {
91+
err := audit.LogCommandEnd(auditID)
92+
if err != nil {
93+
klog.Errorf("failed to log command end to audit: %v", err)
94+
}
95+
}()
8996
// Check whether this is a windows binary (.exe) running inisde WSL.
9097
if runtime.GOOS == "windows" && detect.IsMicrosoftWSL() {
9198
var found = false

pkg/minikube/audit/audit.go

+65-7
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,20 @@ limitations under the License.
1717
package audit
1818

1919
import (
20+
"bufio"
21+
"encoding/json"
22+
"fmt"
2023
"os"
2124
"os/user"
2225
"strings"
2326
"time"
2427

28+
"github.com/google/uuid"
2529
"github.com/spf13/pflag"
2630
"github.com/spf13/viper"
27-
"k8s.io/klog/v2"
2831
"k8s.io/minikube/pkg/minikube/config"
32+
"k8s.io/minikube/pkg/minikube/constants"
33+
"k8s.io/minikube/pkg/minikube/localpath"
2934
"k8s.io/minikube/pkg/version"
3035
)
3136

@@ -52,14 +57,67 @@ func args() string {
5257
}
5358

5459
// Log details about the executed command.
55-
func Log(startTime time.Time) {
56-
if !shouldLog() {
57-
return
60+
func LogCommandStart() (string, error) {
61+
if len(os.Args) < 2 || !shouldLog() {
62+
return "", nil
5863
}
59-
r := newRow(pflag.Arg(0), args(), userName(), version.GetVersion(), startTime, time.Now())
64+
id := uuid.New().String()
65+
r := newRow(pflag.Arg(0), args(), userName(), version.GetVersion(), time.Now(), id)
6066
if err := appendToLog(r); err != nil {
61-
klog.Warning(err)
67+
return "", err
6268
}
69+
return r.id, nil
70+
}
71+
72+
func LogCommandEnd(id string) error {
73+
if id == "" {
74+
return nil
75+
}
76+
if err := openAuditLog(); err != nil {
77+
return err
78+
}
79+
defer closeAuditLog()
80+
var logs []string
81+
s := bufio.NewScanner(currentLogFile)
82+
for s.Scan() {
83+
logs = append(logs, s.Text())
84+
}
85+
if err := s.Err(); err != nil {
86+
return fmt.Errorf("failed to read from audit file: %v", err)
87+
}
88+
closeAuditLog()
89+
rowSlice, err := logsToRows(logs)
90+
if err != nil {
91+
return fmt.Errorf("failed to convert logs to rows: %v", err)
92+
}
93+
auditContents := ""
94+
var entriesNeedsToUpdate int
95+
for _, v := range rowSlice {
96+
if v.id == id {
97+
v.endTime = time.Now().Format(constants.TimeFormat)
98+
v.Data = v.toMap()
99+
entriesNeedsToUpdate++
100+
}
101+
auditLog, err := json.Marshal(v)
102+
if err != nil {
103+
return err
104+
}
105+
auditContents += string(auditLog) + "\n"
106+
}
107+
if entriesNeedsToUpdate == 0 {
108+
return fmt.Errorf("failed to find a log row with id equals to %v", id)
109+
}
110+
// have to truncate the audit log while closed as Windows can't truncate an open file
111+
if err := os.Truncate(localpath.AuditLog(), 0); err != nil {
112+
return fmt.Errorf("failed to truncate audit log: %v", err)
113+
}
114+
if err := openAuditLog(); err != nil {
115+
return err
116+
}
117+
if _, err = currentLogFile.Write([]byte(auditContents)); err != nil {
118+
return fmt.Errorf("failed to write to audit log: %v", err)
119+
}
120+
return nil
63121
}
64122

65123
// shouldLog returns if the command should be logged.
@@ -74,7 +132,7 @@ func shouldLog() bool {
74132
}
75133

76134
// commands that should not be logged.
77-
no := []string{"status", "version"}
135+
no := []string{"status", "version", "logs", "generate-docs"}
78136
a := pflag.Arg(0)
79137
for _, c := range no {
80138
if a == c {

pkg/minikube/audit/audit_test.go

+46-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"os"
2121
"os/user"
2222
"testing"
23-
"time"
2423

2524
"github.com/spf13/pflag"
2625
"github.com/spf13/viper"
@@ -177,7 +176,27 @@ func TestAudit(t *testing.T) {
177176
})
178177

179178
// Check if logging with limited args causes a panic
180-
t.Run("Log", func(t *testing.T) {
179+
t.Run("LogCommandStart", func(t *testing.T) {
180+
oldArgs := os.Args
181+
defer func() { os.Args = oldArgs }()
182+
os.Args = []string{"minikube", "start"}
183+
184+
oldCommandLine := pflag.CommandLine
185+
defer func() {
186+
pflag.CommandLine = oldCommandLine
187+
pflag.Parse()
188+
}()
189+
mockArgs(t, os.Args)
190+
auditID, err := LogCommandStart()
191+
if auditID == "" {
192+
t.Fatal("audit ID should not be empty")
193+
}
194+
if err != nil {
195+
t.Fatal(err)
196+
}
197+
})
198+
199+
t.Run("LogCommandEnd", func(t *testing.T) {
181200
oldArgs := os.Args
182201
defer func() { os.Args = oldArgs }()
183202
os.Args = []string{"minikube"}
@@ -188,8 +207,32 @@ func TestAudit(t *testing.T) {
188207
pflag.Parse()
189208
}()
190209
mockArgs(t, os.Args)
210+
auditID, err := LogCommandStart()
211+
if err != nil {
212+
t.Fatal("start failed")
213+
}
214+
err = LogCommandEnd(auditID)
191215

192-
Log(time.Now())
216+
if err != nil {
217+
t.Fatal(err)
218+
}
219+
})
220+
221+
t.Run("LogCommandEndNonExistingID", func(t *testing.T) {
222+
oldArgs := os.Args
223+
defer func() { os.Args = oldArgs }()
224+
os.Args = []string{"minikube"}
225+
226+
oldCommandLine := pflag.CommandLine
227+
defer func() {
228+
pflag.CommandLine = oldCommandLine
229+
pflag.Parse()
230+
}()
231+
mockArgs(t, os.Args)
232+
err := LogCommandEnd("non-existing-id")
233+
if err == nil {
234+
t.Fatal("function LogCommandEnd should return an error when a non-existing id is passed in it as an argument")
235+
}
193236
})
194237
}
195238

pkg/minikube/audit/logFile.go

+15-8
Original file line numberDiff line numberDiff line change
@@ -20,36 +20,43 @@ import (
2020
"fmt"
2121
"os"
2222

23+
"k8s.io/klog/v2"
2324
"k8s.io/minikube/pkg/minikube/localpath"
2425
"k8s.io/minikube/pkg/minikube/out/register"
2526
)
2627

2728
// currentLogFile the file that's used to store audit logs
2829
var currentLogFile *os.File
2930

30-
// setLogFile sets the logPath and creates the log file if it doesn't exist.
31-
func setLogFile() error {
31+
// openAuditLog opens the audit log file or creates it if it doesn't exist.
32+
func openAuditLog() error {
3233
lp := localpath.AuditLog()
3334
f, err := os.OpenFile(lp, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644)
3435
if err != nil {
35-
return fmt.Errorf("unable to open %s: %v", lp, err)
36+
return fmt.Errorf("failed to open the audit log: %v", err)
3637
}
3738
currentLogFile = f
3839
return nil
3940
}
4041

42+
// closeAuditLog closes the audit log file
43+
func closeAuditLog() {
44+
if err := currentLogFile.Close(); err != nil {
45+
klog.Errorf("failed to close the audit log: %v", err)
46+
}
47+
}
48+
4149
// appendToLog appends the row to the log file.
4250
func appendToLog(row *row) error {
43-
if currentLogFile == nil {
44-
if err := setLogFile(); err != nil {
45-
return err
46-
}
47-
}
4851
ce := register.CloudEvent(row, row.toMap())
4952
bs, err := ce.MarshalJSON()
5053
if err != nil {
5154
return fmt.Errorf("error marshalling event: %v", err)
5255
}
56+
if err := openAuditLog(); err != nil {
57+
return err
58+
}
59+
defer closeAuditLog()
5360
if _, err := currentLogFile.WriteString(string(bs) + "\n"); err != nil {
5461
return fmt.Errorf("unable to write to audit log: %v", err)
5562
}

pkg/minikube/audit/logFile_test.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,23 @@ import (
2323
"testing"
2424
"time"
2525

26+
"github.com/google/uuid"
2627
"k8s.io/minikube/pkg/minikube/localpath"
2728
)
2829

2930
func TestLogFile(t *testing.T) {
30-
t.Run("SetLogFile", func(t *testing.T) {
31+
t.Run("OpenAuditLog", func(t *testing.T) {
3132
// make sure logs directory exists
3233
if err := os.MkdirAll(filepath.Dir(localpath.AuditLog()), 0755); err != nil {
3334
t.Fatalf("Error creating logs directory: %v", err)
3435
}
35-
if err := setLogFile(); err != nil {
36-
t.Error(err)
36+
if err := openAuditLog(); err != nil {
37+
t.Fatal(err)
3738
}
3839
})
3940

4041
t.Run("AppendToLog", func(t *testing.T) {
42+
defer closeAuditLog()
4143
f, err := os.CreateTemp("", "audit.json")
4244
if err != nil {
4345
t.Fatalf("Error creating temporary file: %v", err)
@@ -48,7 +50,7 @@ func TestLogFile(t *testing.T) {
4850
defer func() { currentLogFile = &oldLogFile }()
4951
currentLogFile = f
5052

51-
r := newRow("start", "-v", "user1", "v0.17.1", time.Now(), time.Now())
53+
r := newRow("start", "-v", "user1", "v0.17.1", time.Now(), uuid.New().String())
5254
if err := appendToLog(r); err != nil {
5355
t.Fatalf("Error appendingToLog: %v", err)
5456
}

pkg/minikube/audit/report.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,10 @@ func Report(lastNLines int) (*RawReport, error) {
3232
if lastNLines <= 0 {
3333
return nil, fmt.Errorf("last n lines must be 1 or greater")
3434
}
35-
if currentLogFile == nil {
36-
if err := setLogFile(); err != nil {
37-
return nil, fmt.Errorf("failed to set the log file: %v", err)
38-
}
35+
if err := openAuditLog(); err != nil {
36+
return nil, err
3937
}
38+
defer closeAuditLog()
4039
var logs []string
4140
s := bufio.NewScanner(currentLogFile)
4241
for s.Scan() {

pkg/minikube/audit/row.go

+18-10
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,20 @@ import (
3030

3131
// row is the log of a single command.
3232
type row struct {
33-
args string
34-
command string
35-
endTime string
36-
profile string
37-
startTime string
38-
user string
39-
version string
40-
Data map[string]string `json:"data"`
33+
SpecVersion string `json:"specversion"`
34+
ID string `json:"id"`
35+
Source string `json:"source"`
36+
TypeField string `json:"type"`
37+
DataContentType string `json:"datacontenttype"`
38+
Data map[string]string `json:"data"`
39+
args string
40+
command string
41+
endTime string
42+
id string
43+
profile string
44+
startTime string
45+
user string
46+
version string
4147
}
4248

4349
// Type returns the cloud events compatible type of this struct.
@@ -55,6 +61,7 @@ func (e *row) assignFields() {
5561
e.startTime = e.Data["startTime"]
5662
e.user = e.Data["user"]
5763
e.version = e.Data["version"]
64+
e.id = e.Data["id"]
5865
}
5966

6067
// toMap combines fields into a string map,
@@ -68,23 +75,24 @@ func (e *row) toMap() map[string]string {
6875
"startTime": e.startTime,
6976
"user": e.user,
7077
"version": e.version,
78+
"id": e.id,
7179
}
7280
}
7381

7482
// newRow creates a new audit row.
75-
func newRow(command string, args string, user string, version string, startTime time.Time, endTime time.Time, profile ...string) *row {
83+
func newRow(command string, args string, user string, version string, startTime time.Time, id string, profile ...string) *row {
7684
p := viper.GetString(config.ProfileName)
7785
if len(profile) > 0 {
7886
p = profile[0]
7987
}
8088
return &row{
8189
args: args,
8290
command: command,
83-
endTime: endTime.Format(constants.TimeFormat),
8491
profile: p,
8592
startTime: startTime.Format(constants.TimeFormat),
8693
user: user,
8794
version: version,
95+
id: id,
8896
}
8997
}
9098

0 commit comments

Comments
 (0)