Skip to content

Commit 3d84276

Browse files
authored
Merge pull request kubernetes#129595 from aravindhp/nlq-env-vars
kubelet: use env vars in node log query PS command
2 parents c9f6951 + 12345a1 commit 3d84276

File tree

9 files changed

+142
-37
lines changed

9 files changed

+142
-37
lines changed

pkg/features/kube_features.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,8 @@ const (
446446
// owner: @aravindhp @LorbusChris
447447
// kep: http://kep.k8s.io/2271
448448
//
449-
// Enables querying logs of node services using the /logs endpoint
449+
// Enables querying logs of node services using the /logs endpoint. Enabling this feature has security implications.
450+
// The recommendation is to enable it on a need basis for debugging purposes and disabling otherwise.
450451
NodeLogQuery featuregate.Feature = "NodeLogQuery"
451452

452453
// owner: @iholder101 @kannon92

pkg/generated/openapi/zz_generated.openapi.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/kubelet/apis/config/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,8 @@ type KubeletConfiguration struct {
413413
EnableSystemLogHandler bool
414414
// EnableSystemLogQuery enables the node log query feature on the /logs endpoint.
415415
// EnableSystemLogHandler has to be enabled in addition for this feature to work.
416+
// Enabling this feature has security implications. The recommendation is to enable it on a need basis for debugging
417+
// purposes and disabling otherwise.
416418
// +featureGate=NodeLogQuery
417419
// +optional
418420
EnableSystemLogQuery bool

pkg/kubelet/kubelet_server_journal.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,14 +316,15 @@ func (n *nodeLogQuery) splitNativeVsFileLoggers(ctx context.Context) ([]string,
316316
// copyServiceLogs invokes journalctl or Get-WinEvent with the provided args. Note that
317317
// services are explicitly passed here to account for the heuristics.
318318
func (n *nodeLogQuery) copyServiceLogs(ctx context.Context, w io.Writer, services []string, previousBoot int) {
319-
cmdStr, args, err := getLoggingCmd(n, services)
319+
cmdStr, args, cmdEnv, err := getLoggingCmd(n, services)
320320
if err != nil {
321321
fmt.Fprintf(w, "\nfailed to get logging cmd: %v\n", err)
322322
return
323323
}
324324
cmd := exec.CommandContext(ctx, cmdStr, args...)
325325
cmd.Stdout = w
326326
cmd.Stderr = w
327+
cmd.Env = append(os.Environ(), cmdEnv...)
327328

328329
if err := cmd.Run(); err != nil {
329330
if _, ok := err.(*exec.ExitError); ok {

pkg/kubelet/kubelet_server_journal_linux.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,13 @@ import (
3131
)
3232

3333
// getLoggingCmd returns the journalctl cmd and arguments for the given nodeLogQuery and boot. Note that
34-
// services are explicitly passed here to account for the heuristics
35-
func getLoggingCmd(n *nodeLogQuery, services []string) (string, []string, error) {
36-
args := []string{
34+
// services are explicitly passed here to account for the heuristics.
35+
// The return values are:
36+
// - cmd: the command to be executed
37+
// - args: arguments to the command
38+
// - cmdEnv: environment variables when the command will be executed
39+
func getLoggingCmd(n *nodeLogQuery, services []string) (cmd string, args []string, cmdEnv []string, err error) {
40+
args = []string{
3741
"--utc",
3842
"--no-pager",
3943
"--output=short-precise",
@@ -60,7 +64,7 @@ func getLoggingCmd(n *nodeLogQuery, services []string) (string, []string, error)
6064
args = append(args, "--boot", fmt.Sprintf("%d", *n.Boot))
6165
}
6266

63-
return "journalctl", args, nil
67+
return "journalctl", args, nil, nil
6468
}
6569

6670
// checkForNativeLogger checks journalctl output for a service

pkg/kubelet/kubelet_server_journal_others.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import (
2424
)
2525

2626
// getLoggingCmd on unsupported operating systems returns the echo command and a warning message (as strings)
27-
func getLoggingCmd(_ *nodeLogQuery, _ []string) (string, []string, error) {
28-
return "", []string{}, errors.New("Operating System Not Supported")
27+
func getLoggingCmd(_ *nodeLogQuery, _ []string) (cmd string, args []string, cmdEnv []string, err error) {
28+
return "", args, cmdEnv, errors.New("Operating System Not Supported")
2929
}
3030

3131
// checkForNativeLogger on unsupported operating systems returns false

pkg/kubelet/kubelet_server_journal_test.go

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,31 +35,62 @@ import (
3535
)
3636

3737
func Test_getLoggingCmd(t *testing.T) {
38+
var emptyCmdEnv []string
3839
tests := []struct {
39-
name string
40-
args nodeLogQuery
41-
wantLinux []string
42-
wantWindows []string
43-
wantOtherOS []string
40+
name string
41+
args nodeLogQuery
42+
services []string
43+
wantLinux []string
44+
wantWindows []string
45+
wantLinuxCmdEnv []string
46+
wantWindowsCmdEnv []string
4447
}{
4548
{
46-
args: nodeLogQuery{},
47-
wantLinux: []string{"--utc", "--no-pager", "--output=short-precise"},
48-
wantWindows: []string{"-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", "Get-WinEvent -FilterHashtable @{LogName='Application'} | Sort-Object TimeCreated | Format-Table -AutoSize -Wrap"},
49+
name: "basic",
50+
args: nodeLogQuery{},
51+
services: []string{},
52+
wantLinux: []string{"--utc", "--no-pager", "--output=short-precise"},
53+
wantLinuxCmdEnv: emptyCmdEnv,
54+
wantWindows: []string{"-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", "Get-WinEvent -FilterHashtable @{LogName='Application'} | Sort-Object TimeCreated | Format-Table -AutoSize -Wrap"},
55+
wantWindowsCmdEnv: emptyCmdEnv,
56+
},
57+
{
58+
name: "two providers",
59+
args: nodeLogQuery{},
60+
services: []string{"p1", "p2"},
61+
wantLinux: []string{"--utc", "--no-pager", "--output=short-precise", "--unit=p1", "--unit=p2"},
62+
wantLinuxCmdEnv: emptyCmdEnv,
63+
wantWindows: []string{"-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", "Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName=$Env:kubelet_provider0,$Env:kubelet_provider1} | Sort-Object TimeCreated | Format-Table -AutoSize -Wrap"},
64+
wantWindowsCmdEnv: []string{"kubelet_provider0=p1", "kubelet_provider1=p2"},
65+
},
66+
{
67+
name: "empty provider",
68+
args: nodeLogQuery{},
69+
services: []string{"p1", "", "p2"},
70+
wantLinux: []string{"--utc", "--no-pager", "--output=short-precise", "--unit=p1", "--unit=p2"},
71+
wantLinuxCmdEnv: emptyCmdEnv,
72+
wantWindows: []string{"-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", "Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName=$Env:kubelet_provider0,$Env:kubelet_provider2} | Sort-Object TimeCreated | Format-Table -AutoSize -Wrap"},
73+
wantWindowsCmdEnv: []string{"kubelet_provider0=p1", "kubelet_provider2=p2"},
4974
},
5075
}
5176
for _, tt := range tests {
5277
t.Run(tt.name, func(t *testing.T) {
53-
_, got, err := getLoggingCmd(&tt.args, []string{})
78+
_, got, gotCmdEnv, err := getLoggingCmd(&tt.args, tt.services)
5479
switch os := runtime.GOOS; os {
5580
case "linux":
5681
if !reflect.DeepEqual(got, tt.wantLinux) {
5782
t.Errorf("getLoggingCmd() = %v, want %v", got, tt.wantLinux)
5883
}
84+
if !reflect.DeepEqual(gotCmdEnv, tt.wantLinuxCmdEnv) {
85+
t.Errorf("gotCmdEnv %v, wantLinuxCmdEnv %v", gotCmdEnv, tt.wantLinuxCmdEnv)
86+
}
5987
case "windows":
6088
if !reflect.DeepEqual(got, tt.wantWindows) {
6189
t.Errorf("getLoggingCmd() = %v, want %v", got, tt.wantWindows)
6290
}
91+
if !reflect.DeepEqual(gotCmdEnv, tt.wantWindowsCmdEnv) {
92+
t.Errorf("gotCmdEnv %v, wantWindowsCmdEnv %v", gotCmdEnv, tt.wantWindowsCmdEnv)
93+
}
6394
default:
6495
if err == nil {
6596
t.Errorf("getLoggingCmd() = %v, want err", got)

pkg/kubelet/kubelet_server_journal_windows.go

Lines changed: 83 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,43 +27,107 @@ import (
2727

2828
const powershellExe = "PowerShell.exe"
2929

30-
// getLoggingCmd returns the powershell cmd and arguments for the given nodeLogQuery and boot
31-
func getLoggingCmd(n *nodeLogQuery, services []string) (string, []string, error) {
32-
args := []string{
30+
// getLoggingCmd returns the powershell cmd, arguments, and environment variables for the given nodeLogQuery and boot.
31+
// All string inputs are environment variables to stop subcommands expressions from being executed.
32+
// The return values are:
33+
// - cmd: the command to be executed
34+
// - args: arguments to the command
35+
// - cmdEnv: environment variables when the command will be executed
36+
func getLoggingCmd(n *nodeLogQuery, services []string) (cmd string, args []string, cmdEnv []string, err error) {
37+
cmdEnv = getLoggingCmdEnv(n, services)
38+
39+
var includeSinceTime, includeUntilTime, includeTailLines, includePattern bool
40+
if n.SinceTime != nil {
41+
includeSinceTime = true
42+
}
43+
if n.UntilTime != nil {
44+
includeUntilTime = true
45+
}
46+
if n.TailLines != nil {
47+
includeTailLines = true
48+
}
49+
if len(n.Pattern) > 0 {
50+
includePattern = true
51+
}
52+
53+
var includeServices []bool
54+
for _, service := range services {
55+
includeServices = append(includeServices, len(service) > 0)
56+
}
57+
58+
args = getLoggingCmdArgs(includeSinceTime, includeUntilTime, includeTailLines, includePattern, includeServices)
59+
60+
return powershellExe, args, cmdEnv, nil
61+
}
62+
63+
// getLoggingCmdArgs returns arguments that need to be passed to powershellExe
64+
func getLoggingCmdArgs(includeSinceTime, includeUntilTime, includeTailLines, includePattern bool, services []bool) (args []string) {
65+
args = []string{
3366
"-NonInteractive",
3467
"-ExecutionPolicy", "Bypass",
3568
"-Command",
3669
}
3770

38-
psCmd := "Get-WinEvent -FilterHashtable @{LogName='Application'"
39-
if n.SinceTime != nil {
40-
psCmd += fmt.Sprintf("; StartTime='%s'", n.SinceTime.Format(dateLayout))
71+
psCmd := `Get-WinEvent -FilterHashtable @{LogName='Application'`
72+
73+
if includeSinceTime {
74+
psCmd += fmt.Sprintf(`; StartTime="$Env:kubelet_sinceTime"`)
4175
}
42-
if n.UntilTime != nil {
43-
psCmd += fmt.Sprintf("; EndTime='%s'", n.UntilTime.Format(dateLayout))
76+
if includeUntilTime {
77+
psCmd += fmt.Sprintf(`; EndTime="$Env:kubelet_untilTime"`)
4478
}
79+
4580
var providers []string
46-
for _, service := range services {
47-
if len(service) > 0 {
48-
providers = append(providers, "'"+service+"'")
81+
for i := range services {
82+
if services[i] {
83+
providers = append(providers, fmt.Sprintf("$Env:kubelet_provider%d", i))
4984
}
5085
}
86+
5187
if len(providers) > 0 {
5288
psCmd += fmt.Sprintf("; ProviderName=%s", strings.Join(providers, ","))
5389
}
54-
psCmd += "}"
55-
if n.TailLines != nil {
56-
psCmd += fmt.Sprintf(" -MaxEvents %d", *n.TailLines)
90+
91+
psCmd += `}`
92+
if includeTailLines {
93+
psCmd += fmt.Sprint(` -MaxEvents $Env:kubelet_tailLines`)
5794
}
58-
psCmd += " | Sort-Object TimeCreated"
59-
if len(n.Pattern) > 0 {
60-
psCmd += fmt.Sprintf(" | Where-Object -Property Message -Match '%s'", n.Pattern)
95+
psCmd += ` | Sort-Object TimeCreated`
96+
97+
if includePattern {
98+
psCmd += fmt.Sprintf(` | Where-Object -Property Message -Match "$Env:kubelet_pattern"`)
6199
}
62-
psCmd += " | Format-Table -AutoSize -Wrap"
100+
psCmd += ` | Format-Table -AutoSize -Wrap`
63101

64102
args = append(args, psCmd)
65103

66-
return powershellExe, args, nil
104+
return args
105+
}
106+
107+
// getLoggingCmdEnv returns the environment variables that will be present when powershellExe is executed
108+
func getLoggingCmdEnv(n *nodeLogQuery, services []string) (cmdEnv []string) {
109+
if n.SinceTime != nil {
110+
cmdEnv = append(cmdEnv, fmt.Sprintf("kubelet_sinceTime=%s", n.SinceTime.Format(dateLayout)))
111+
}
112+
if n.UntilTime != nil {
113+
cmdEnv = append(cmdEnv, fmt.Sprintf("kubelet_untilTime=%s", n.UntilTime.Format(dateLayout)))
114+
}
115+
116+
for i, service := range services {
117+
if len(service) > 0 {
118+
cmdEnv = append(cmdEnv, fmt.Sprintf("kubelet_provider%d=%s", i, service))
119+
}
120+
}
121+
122+
if n.TailLines != nil {
123+
cmdEnv = append(cmdEnv, fmt.Sprintf("kubelet_tailLines=%d", *n.TailLines))
124+
}
125+
126+
if len(n.Pattern) > 0 {
127+
cmdEnv = append(cmdEnv, fmt.Sprintf("kubelet_pattern=%s", n.Pattern))
128+
}
129+
130+
return cmdEnv
67131
}
68132

69133
// checkForNativeLogger always returns true for Windows

staging/src/k8s.io/kubelet/config/v1beta1/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,8 @@ type KubeletConfiguration struct {
726726
EnableSystemLogHandler *bool `json:"enableSystemLogHandler,omitempty"`
727727
// enableSystemLogQuery enables the node log query feature on the /logs endpoint.
728728
// EnableSystemLogHandler has to be enabled in addition for this feature to work.
729+
// Enabling this feature has security implications. The recommendation is to enable it on a need basis for debugging
730+
// purposes and disabling otherwise.
729731
// Default: false
730732
// +featureGate=NodeLogQuery
731733
// +optional

0 commit comments

Comments
 (0)