Skip to content

Commit a4f0350

Browse files
committed
WIP: Add user flag and log executed commands
1 parent 857e0a2 commit a4f0350

37 files changed

+342
-7
lines changed

cmd/minikube/cmd/root.go

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

2728
"github.com/spf13/cobra"
2829
"github.com/spf13/pflag"
@@ -31,6 +32,7 @@ import (
3132
"k8s.io/kubectl/pkg/util/templates"
3233
configCmd "k8s.io/minikube/cmd/minikube/cmd/config"
3334
"k8s.io/minikube/pkg/drivers/kic/oci"
35+
"k8s.io/minikube/pkg/minikube/audit"
3436
"k8s.io/minikube/pkg/minikube/config"
3537
"k8s.io/minikube/pkg/minikube/constants"
3638
"k8s.io/minikube/pkg/minikube/exit"
@@ -68,6 +70,8 @@ var RootCmd = &cobra.Command{
6870
// Execute adds all child commands to the root command sets flags appropriately.
6971
// This is called by main.main(). It only needs to happen once to the rootCmd.
7072
func Execute() {
73+
defer audit.Log(time.Now())
74+
7175
_, callingCmd := filepath.Split(os.Args[0])
7276

7377
if callingCmd == "kubectl" {
@@ -170,6 +174,7 @@ func init() {
170174

171175
RootCmd.PersistentFlags().StringP(config.ProfileName, "p", constants.DefaultClusterName, `The name of the minikube VM being used. This can be set to allow having multiple instances of minikube independently.`)
172176
RootCmd.PersistentFlags().StringP(configCmd.Bootstrapper, "b", "kubeadm", "The name of the cluster bootstrapper that will set up the Kubernetes cluster.")
177+
RootCmd.PersistentFlags().String(config.User, "", "Specifies the user executing the operation. Useful for auditing operations executed by 3rd party tools. Defaults to the operating system username.")
173178

174179
groups := templates.CommandGroups{
175180
{

go.sum

-2
Original file line numberDiff line numberDiff line change
@@ -986,8 +986,6 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1
986986
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
987987
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
988988
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
989-
github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok=
990-
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
991989
github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ=
992990
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
993991
github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=

pkg/minikube/audit/audit.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package audit
18+
19+
import (
20+
"os"
21+
"os/user"
22+
"strings"
23+
"time"
24+
25+
"github.com/spf13/viper"
26+
"k8s.io/klog"
27+
"k8s.io/minikube/pkg/minikube/config"
28+
)
29+
30+
// username pulls the user flag, if empty gets the os username.
31+
func username() string {
32+
u := viper.GetString(config.User)
33+
if u != "" {
34+
return u
35+
}
36+
osUser, err := user.Current()
37+
if err != nil {
38+
return "UNKNOWN"
39+
}
40+
return osUser.Username
41+
}
42+
43+
// args concats the args into space delimited string.
44+
func args() string {
45+
if len(os.Args) < 3 {
46+
return ""
47+
}
48+
return strings.Join(os.Args[2:], " ")
49+
}
50+
51+
// Log details about the executed command.
52+
func Log(startTime time.Time) {
53+
if !shouldLog() {
54+
return
55+
}
56+
e := newEntry(os.Args[1], args(), username(), startTime, time.Now())
57+
if err := appendToLog(e); err != nil {
58+
klog.Error(err)
59+
}
60+
}
61+
62+
// shouldLog returns if the command should be logged.
63+
func shouldLog() bool {
64+
// commands that should not be logged.
65+
no := []string{"status", "version"}
66+
a := os.Args[1]
67+
for _, c := range no {
68+
if a == c {
69+
return false
70+
}
71+
}
72+
return true
73+
}

pkg/minikube/audit/audit_test.go

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package audit
18+
19+
import (
20+
"os"
21+
"os/user"
22+
"testing"
23+
24+
"github.com/spf13/viper"
25+
"k8s.io/minikube/pkg/minikube/config"
26+
)
27+
28+
func TestAudit(t *testing.T) {
29+
t.Run("UsernameSetFlag", func(t *testing.T) {
30+
want := "test123"
31+
viper.Set(config.User, want)
32+
33+
got := username()
34+
35+
if got != want {
36+
t.Errorf("username() = %q; want %q", got, want)
37+
}
38+
})
39+
40+
t.Run("UsernameOS", func(t *testing.T) {
41+
viper.Set(config.User, "")
42+
43+
u, err := user.Current()
44+
if err != nil {
45+
t.Fatal(err)
46+
}
47+
want := u.Username
48+
49+
got := username()
50+
51+
if got != want {
52+
t.Errorf("username() = %q; want %q", got, want)
53+
}
54+
})
55+
56+
t.Run("ArgsNone", func(t *testing.T) {
57+
oldArgs := os.Args
58+
defer func() { os.Args = oldArgs }()
59+
os.Args = []string{"minikube", "start"}
60+
61+
want := ""
62+
63+
got := args()
64+
65+
if got != want {
66+
t.Errorf("args() = %q; want %q", got, want)
67+
}
68+
})
69+
70+
t.Run("Args", func(t *testing.T) {
71+
oldArgs := os.Args
72+
defer func() { os.Args = oldArgs }()
73+
os.Args = []string{"minikube", "start", "--user", "test123"}
74+
75+
want := "--user test123"
76+
77+
got := args()
78+
79+
if got != want {
80+
t.Errorf("args() = %q; want %q", got, want)
81+
}
82+
})
83+
}

pkg/minikube/audit/entry.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package audit
18+
19+
import "time"
20+
21+
// entry represents the execution of a command.
22+
type entry struct {
23+
data map[string]string
24+
}
25+
26+
// Type returns the cloud events compatible type of this struct.
27+
func (e *entry) Type() string {
28+
return "io.k8s.sigs.minikube.audit"
29+
}
30+
31+
// newEntry returns a new audit type.
32+
func newEntry(command string, args string, user string, startTime time.Time, endTime time.Time) *entry {
33+
return &entry{
34+
map[string]string{
35+
"args": args,
36+
"command": command,
37+
"endTime": endTime.String(),
38+
"startTime": startTime.String(),
39+
"user": user,
40+
},
41+
}
42+
}

pkg/minikube/audit/logFile.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package audit
18+
19+
import (
20+
"fmt"
21+
"os"
22+
23+
"k8s.io/minikube/pkg/minikube/localpath"
24+
"k8s.io/minikube/pkg/minikube/out/register"
25+
)
26+
27+
var (
28+
file *os.File
29+
logPath string
30+
)
31+
32+
// setLogFile sets the logPath and creates the log file if it doesn't exist.
33+
func setLogFile() error {
34+
logPath = localpath.AuditLog()
35+
if _, err := os.Stat(logPath); os.IsNotExist(err) {
36+
return createLogFile()
37+
}
38+
f, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
39+
if err != nil {
40+
return fmt.Errorf("unable to open %s: %v", logPath, err)
41+
}
42+
file = f
43+
return nil
44+
}
45+
46+
// createLogFile creates the file used for logging audits.
47+
func createLogFile() error {
48+
f, err := os.Create(logPath)
49+
if err != nil {
50+
return fmt.Errorf("unable to create file %s: %v", logPath, err)
51+
}
52+
file = f
53+
return nil
54+
}
55+
56+
// appendToLog appends the audit entry to the log file.
57+
func appendToLog(entry *entry) error {
58+
if file == nil {
59+
if err := setLogFile(); err != nil {
60+
return err
61+
}
62+
}
63+
e := register.CloudEvent(entry, entry.data)
64+
bs, err := e.MarshalJSON()
65+
if err != nil {
66+
return fmt.Errorf("error marshalling event: %v", err)
67+
}
68+
if _, err := file.WriteString(string(bs) + "\n"); err != nil {
69+
return fmt.Errorf("unable to write to %s: %v", logPath, err)
70+
}
71+
return nil
72+
}

pkg/minikube/config/config.go

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ const (
4848
ShowDriverDeprecationNotification = "ShowDriverDeprecationNotification"
4949
// ShowBootstrapperDeprecationNotification is the key for ShowBootstrapperDeprecationNotification
5050
ShowBootstrapperDeprecationNotification = "ShowBootstrapperDeprecationNotification"
51+
// User represents the key for the global user parameter
52+
User = "user"
5153
)
5254

5355
var (

pkg/minikube/localpath/localpath.go

+5
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ func EventLog(name string) string {
6767
return filepath.Join(Profile(name), "events.json")
6868
}
6969

70+
// AuditLog returns the path to the audit log.
71+
func AuditLog() string {
72+
return filepath.Join(MiniPath(), "logs", "audit.json")
73+
}
74+
7075
// ClientCert returns client certificate path, used by kubeconfig
7176
func ClientCert(name string) string {
7277
new := filepath.Join(Profile(name), "client.crt")

pkg/minikube/out/register/cloud_events.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ func SetEventLogPath(path string) {
6363
eventFile = f
6464
}
6565

66-
// cloudEvent creates a CloudEvent from a log object & associated data
67-
func cloudEvent(log Log, data map[string]string) cloudevents.Event {
66+
// CloudEvent creates a CloudEvent from a log object & associated data
67+
func CloudEvent(log Log, data map[string]string) cloudevents.Event {
6868
event := cloudevents.NewEvent()
6969
event.SetSource("https://minikube.sigs.k8s.io/")
7070
event.SetType(log.Type())
@@ -78,7 +78,7 @@ func cloudEvent(log Log, data map[string]string) cloudevents.Event {
7878

7979
// print JSON output to configured writer
8080
func printAsCloudEvent(log Log, data map[string]string) {
81-
event := cloudEvent(log, data)
81+
event := CloudEvent(log, data)
8282

8383
bs, err := event.MarshalJSON()
8484
if err != nil {
@@ -90,7 +90,7 @@ func printAsCloudEvent(log Log, data map[string]string) {
9090

9191
// print JSON output to configured writer, and record it to disk
9292
func printAndRecordCloudEvent(log Log, data map[string]string) {
93-
event := cloudEvent(log, data)
93+
event := CloudEvent(log, data)
9494

9595
bs, err := event.MarshalJSON()
9696
if err != nil {
@@ -118,7 +118,7 @@ func recordCloudEvent(log Log, data map[string]string) {
118118
}
119119

120120
go func() {
121-
event := cloudEvent(log, data)
121+
event := CloudEvent(log, data)
122122
bs, err := event.MarshalJSON()
123123
if err != nil {
124124
klog.Errorf("error marshalling event: %v", err)

0 commit comments

Comments
 (0)