Skip to content
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

Write commands to audit log regardless of whether it terminates cleanly #13307

Merged
merged 7 commits into from
Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions cmd/minikube/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"path/filepath"
"runtime"
"strings"
"time"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
Expand Down Expand Up @@ -84,8 +83,16 @@ var RootCmd = &cobra.Command{
// Execute adds all child commands to the root command sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
defer audit.Log(time.Now())

auditID, err := audit.LogCommandStart()
if err != nil {
klog.Errorf("failed to log command start to audit: %v", err)
spowelljr marked this conversation as resolved.
Show resolved Hide resolved
spowelljr marked this conversation as resolved.
Show resolved Hide resolved
}
defer func() {
err := audit.LogCommandEnd(auditID)
spowelljr marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
klog.Errorf("failed to log command end to audit: %v", err)
spowelljr marked this conversation as resolved.
Show resolved Hide resolved
spowelljr marked this conversation as resolved.
Show resolved Hide resolved
}
}()
// Check whether this is a windows binary (.exe) running inisde WSL.
if runtime.GOOS == "windows" && detect.IsMicrosoftWSL() {
var found = false
Expand Down
72 changes: 65 additions & 7 deletions pkg/minikube/audit/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,20 @@ limitations under the License.
package audit

import (
"bufio"
"encoding/json"
"fmt"
"os"
"os/user"
"strings"
"time"

"github.com/google/uuid"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"k8s.io/klog/v2"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/minikube/localpath"
"k8s.io/minikube/pkg/version"
)

Expand All @@ -52,14 +57,67 @@ func args() string {
}

// Log details about the executed command.
func Log(startTime time.Time) {
if !shouldLog() {
return
func LogCommandStart() (string, error) {
if len(os.Args) < 2 || !shouldLog() {
return "", nil
}
r := newRow(pflag.Arg(0), args(), userName(), version.GetVersion(), startTime, time.Now())
id := uuid.New().String()
r := newRow(pflag.Arg(0), args(), userName(), version.GetVersion(), time.Now(), id)
if err := appendToLog(r); err != nil {
klog.Warning(err)
return "", err
}
return r.id, nil
}

func LogCommandEnd(id string) error {
spowelljr marked this conversation as resolved.
Show resolved Hide resolved
if id == "" {
return nil
}
if err := openAuditLog(); err != nil {
return err
}
defer closeAuditLog()
var logs []string
s := bufio.NewScanner(currentLogFile)
for s.Scan() {
logs = append(logs, s.Text())
}
if err := s.Err(); err != nil {
return fmt.Errorf("failed to read from audit file: %v", err)
}
closeAuditLog()
rowSlice, err := logsToRows(logs)
if err != nil {
return fmt.Errorf("failed to convert logs to rows: %v", err)
}
auditContents := ""
var entriesNeedsToUpdate int
for _, v := range rowSlice {
if v.id == id {
v.endTime = time.Now().Format(constants.TimeFormat)
v.Data = v.toMap()
entriesNeedsToUpdate++
}
auditLog, err := json.Marshal(v)
if err != nil {
return err
}
auditContents += string(auditLog) + "\n"
}
if entriesNeedsToUpdate == 0 {
return fmt.Errorf("failed to find a log row with id equals to %v", id)
}
// have to truncate the audit log while closed as Windows can't truncate an open file
if err := os.Truncate(localpath.AuditLog(), 0); err != nil {
return fmt.Errorf("failed to truncate audit log: %v", err)
}
if err := openAuditLog(); err != nil {
return err
}
spowelljr marked this conversation as resolved.
Show resolved Hide resolved
if _, err = currentLogFile.Write([]byte(auditContents)); err != nil {
return fmt.Errorf("failed to write to audit log: %v", err)
}
return nil
spowelljr marked this conversation as resolved.
Show resolved Hide resolved
}

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

// commands that should not be logged.
no := []string{"status", "version"}
no := []string{"status", "version", "logs", "generate-docs"}
a := pflag.Arg(0)
for _, c := range no {
if a == c {
Expand Down
49 changes: 46 additions & 3 deletions pkg/minikube/audit/audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"os"
"os/user"
"testing"
"time"

"github.com/spf13/pflag"
"github.com/spf13/viper"
Expand Down Expand Up @@ -177,7 +176,27 @@ func TestAudit(t *testing.T) {
})

// Check if logging with limited args causes a panic
t.Run("Log", func(t *testing.T) {
t.Run("LogCommandStart", func(t *testing.T) {
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"minikube", "start"}

oldCommandLine := pflag.CommandLine
defer func() {
pflag.CommandLine = oldCommandLine
pflag.Parse()
}()
mockArgs(t, os.Args)
auditID, err := LogCommandStart()
if auditID == "" {
t.Fatal("audit ID should not be empty")
}
if err != nil {
t.Fatal(err)
}
})

t.Run("LogCommandEnd", func(t *testing.T) {
spowelljr marked this conversation as resolved.
Show resolved Hide resolved
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"minikube"}
Expand All @@ -188,8 +207,32 @@ func TestAudit(t *testing.T) {
pflag.Parse()
}()
mockArgs(t, os.Args)
auditID, err := LogCommandStart()
if err != nil {
t.Fatal("start failed")
}
err = LogCommandEnd(auditID)

Log(time.Now())
if err != nil {
t.Fatal(err)
}
})

t.Run("LogCommandEndNonExistingID", func(t *testing.T) {
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"minikube"}

oldCommandLine := pflag.CommandLine
defer func() {
pflag.CommandLine = oldCommandLine
pflag.Parse()
}()
mockArgs(t, os.Args)
err := LogCommandEnd("non-existing-id")
if err == nil {
t.Fatal("function LogCommandEnd should return an error when a non-existing id is passed in it as an argument")
}
})
}

Expand Down
23 changes: 15 additions & 8 deletions pkg/minikube/audit/logFile.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,43 @@ import (
"fmt"
"os"

"k8s.io/klog/v2"
"k8s.io/minikube/pkg/minikube/localpath"
"k8s.io/minikube/pkg/minikube/out/register"
)

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

// setLogFile sets the logPath and creates the log file if it doesn't exist.
func setLogFile() error {
// openAuditLog opens the audit log file or creates it if it doesn't exist.
func openAuditLog() error {
lp := localpath.AuditLog()
f, err := os.OpenFile(lp, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
return fmt.Errorf("unable to open %s: %v", lp, err)
return fmt.Errorf("failed to open the audit log: %v", err)
}
currentLogFile = f
return nil
}

// closeAuditLog closes the audit log file
func closeAuditLog() {
if err := currentLogFile.Close(); err != nil {
klog.Errorf("failed to close the audit log: %v", err)
}
}

// appendToLog appends the row to the log file.
func appendToLog(row *row) error {
if currentLogFile == nil {
if err := setLogFile(); err != nil {
return err
}
}
ce := register.CloudEvent(row, row.toMap())
bs, err := ce.MarshalJSON()
if err != nil {
return fmt.Errorf("error marshalling event: %v", err)
}
if err := openAuditLog(); err != nil {
return err
}
defer closeAuditLog()
if _, err := currentLogFile.WriteString(string(bs) + "\n"); err != nil {
return fmt.Errorf("unable to write to audit log: %v", err)
}
Expand Down
10 changes: 6 additions & 4 deletions pkg/minikube/audit/logFile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,23 @@ import (
"testing"
"time"

"github.com/google/uuid"
"k8s.io/minikube/pkg/minikube/localpath"
)

func TestLogFile(t *testing.T) {
t.Run("SetLogFile", func(t *testing.T) {
t.Run("OpenAuditLog", func(t *testing.T) {
// make sure logs directory exists
if err := os.MkdirAll(filepath.Dir(localpath.AuditLog()), 0755); err != nil {
t.Fatalf("Error creating logs directory: %v", err)
}
if err := setLogFile(); err != nil {
t.Error(err)
if err := openAuditLog(); err != nil {
t.Fatal(err)
}
})

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

r := newRow("start", "-v", "user1", "v0.17.1", time.Now(), time.Now())
r := newRow("start", "-v", "user1", "v0.17.1", time.Now(), uuid.New().String())
if err := appendToLog(r); err != nil {
t.Fatalf("Error appendingToLog: %v", err)
}
Expand Down
7 changes: 3 additions & 4 deletions pkg/minikube/audit/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,10 @@ func Report(lastNLines int) (*RawReport, error) {
if lastNLines <= 0 {
return nil, fmt.Errorf("last n lines must be 1 or greater")
}
if currentLogFile == nil {
if err := setLogFile(); err != nil {
return nil, fmt.Errorf("failed to set the log file: %v", err)
}
if err := openAuditLog(); err != nil {
return nil, err
}
defer closeAuditLog()
var logs []string
s := bufio.NewScanner(currentLogFile)
for s.Scan() {
Expand Down
28 changes: 18 additions & 10 deletions pkg/minikube/audit/row.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,20 @@ import (

// row is the log of a single command.
type row struct {
args string
command string
endTime string
profile string
startTime string
user string
version string
Data map[string]string `json:"data"`
SpecVersion string `json:"specversion"`
ID string `json:"id"`
Source string `json:"source"`
TypeField string `json:"type"`
DataContentType string `json:"datacontenttype"`
Data map[string]string `json:"data"`
args string
command string
endTime string
id string
profile string
startTime string
user string
version string
}

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

// toMap combines fields into a string map,
Expand All @@ -68,23 +75,24 @@ func (e *row) toMap() map[string]string {
"startTime": e.startTime,
"user": e.user,
"version": e.version,
"id": e.id,
}
}

// newRow creates a new audit row.
func newRow(command string, args string, user string, version string, startTime time.Time, endTime time.Time, profile ...string) *row {
func newRow(command string, args string, user string, version string, startTime time.Time, id string, profile ...string) *row {
p := viper.GetString(config.ProfileName)
if len(profile) > 0 {
p = profile[0]
}
return &row{
args: args,
command: command,
endTime: endTime.Format(constants.TimeFormat),
profile: p,
startTime: startTime.Format(constants.TimeFormat),
user: user,
version: version,
id: id,
}
}

Expand Down
Loading