mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-19 18:02:01 +00:00
Escape terminal special characters in kubectl (#112553)
* Escape terminal special characters in kubectl * Add escaping for kubectl alpha events
This commit is contained in:
parent
90f50488c7
commit
dad0e937c0
@ -212,18 +212,19 @@ func printTable(table *metav1.Table, output io.Writer, options PrintOptions) err
|
||||
case string:
|
||||
print := val
|
||||
truncated := false
|
||||
// truncate at newlines
|
||||
newline := strings.Index(print, "\n")
|
||||
if newline >= 0 {
|
||||
// Truncate at the first newline, carriage return or formfeed
|
||||
// (treated as a newline by tabwriter).
|
||||
breakchar := strings.IndexAny(print, "\f\n\r")
|
||||
if breakchar >= 0 {
|
||||
truncated = true
|
||||
print = print[:newline]
|
||||
print = print[:breakchar]
|
||||
}
|
||||
fmt.Fprint(output, print)
|
||||
WriteEscaped(output, print)
|
||||
if truncated {
|
||||
fmt.Fprint(output, "...")
|
||||
}
|
||||
default:
|
||||
fmt.Fprint(output, val)
|
||||
WriteEscaped(output, fmt.Sprint(val))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -769,6 +769,18 @@ test1 20h This is first line which is long and goes for on and on and on an
|
||||
},
|
||||
expected: `NAME AGE DESCRIPTION
|
||||
test1 20h This is first...
|
||||
`,
|
||||
},
|
||||
// terminal special character, should be escaped
|
||||
{
|
||||
columns: []metav1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string"},
|
||||
},
|
||||
rows: []metav1.TableRow{
|
||||
{Cells: []interface{}{"test1\x1b"}},
|
||||
},
|
||||
expected: `NAME
|
||||
test1^[
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
39
staging/src/k8s.io/cli-runtime/pkg/printers/terminal.go
Normal file
39
staging/src/k8s.io/cli-runtime/pkg/printers/terminal.go
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package printers
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// terminalEscaper replaces ANSI escape sequences and other terminal special
|
||||
// characters to avoid terminal escape character attacks (issue #101695).
|
||||
var terminalEscaper = strings.NewReplacer("\x1b", "^[", "\r", "\\r")
|
||||
|
||||
// WriteEscaped replaces unsafe terminal characters with replacement strings
|
||||
// and writes them to the given writer.
|
||||
func WriteEscaped(writer io.Writer, output string) error {
|
||||
_, err := terminalEscaper.WriteString(writer, output)
|
||||
return err
|
||||
}
|
||||
|
||||
// EscapeTerminal escapes terminal special characters in a human readable (but
|
||||
// non-reversible) format.
|
||||
func EscapeTerminal(in string) string {
|
||||
return terminalEscaper.Replace(in)
|
||||
}
|
@ -26,6 +26,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/duration"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
)
|
||||
|
||||
// EventPrinter stores required fields to be used for
|
||||
@ -72,10 +73,11 @@ func (ep *EventPrinter) printOneEvent(w io.Writer, e corev1.Event) {
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s/%s\t%v\n",
|
||||
interval,
|
||||
e.Type,
|
||||
e.Reason,
|
||||
e.InvolvedObject.Kind, e.InvolvedObject.Name,
|
||||
strings.TrimSpace(e.Message),
|
||||
printers.EscapeTerminal(e.Type),
|
||||
printers.EscapeTerminal(e.Reason),
|
||||
printers.EscapeTerminal(e.InvolvedObject.Kind),
|
||||
printers.EscapeTerminal(e.InvolvedObject.Name),
|
||||
printers.EscapeTerminal(strings.TrimSpace(e.Message)),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -208,6 +208,40 @@ foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica
|
||||
},
|
||||
},
|
||||
expected: `foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
`,
|
||||
},
|
||||
{
|
||||
printer: EventPrinter{
|
||||
NoHeaders: false,
|
||||
AllNamespaces: false,
|
||||
},
|
||||
obj: &corev1.EventList{
|
||||
Items: []corev1.Event{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bar-000",
|
||||
Namespace: "foo",
|
||||
},
|
||||
InvolvedObject: corev1.ObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "bar\x1b",
|
||||
Namespace: "foo",
|
||||
},
|
||||
Type: "test\x1b",
|
||||
Reason: "test\x1b",
|
||||
Message: "\x1b",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
Count: 3,
|
||||
LastObservedTime: metav1.NewMicroTime(time.Now().Add(-1 * time.Minute)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: `LAST SEEN TYPE REASON OBJECT MESSAGE
|
||||
60s (x3 over 20m) test^[ test^[ Deployment/bar^[ ^[
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
@ -252,7 +252,7 @@ func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jso
|
||||
}
|
||||
for arrIx := range values {
|
||||
for valIx := range values[arrIx] {
|
||||
valueStrings = append(valueStrings, fmt.Sprintf("%v", values[arrIx][valIx].Interface()))
|
||||
valueStrings = append(valueStrings, printers.EscapeTerminal(fmt.Sprint(values[arrIx][valIx].Interface())))
|
||||
}
|
||||
}
|
||||
columns[ix] = strings.Join(valueStrings, ",")
|
||||
|
@ -311,6 +311,22 @@ foo baz
|
||||
obj: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, TypeMeta: metav1.TypeMeta{APIVersion: "baz"}},
|
||||
expectedOutput: `NAME API_VERSION NOT_FOUND
|
||||
foo baz <none>
|
||||
`,
|
||||
},
|
||||
{
|
||||
columns: []Column{
|
||||
{
|
||||
Header: "NAME",
|
||||
FieldSpec: "{.metadata.name}",
|
||||
},
|
||||
},
|
||||
obj: &corev1.PodList{
|
||||
Items: []corev1.Pod{
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "\x1b \r"}},
|
||||
},
|
||||
},
|
||||
expectedOutput: `NAME
|
||||
^[ \r
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
@ -65,6 +65,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
runtimeresource "k8s.io/cli-runtime/pkg/resource"
|
||||
"k8s.io/client-go/dynamic"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
@ -148,11 +149,13 @@ func (pw *prefixWriter) Write(level int, format string, a ...interface{}) {
|
||||
for i := 0; i < level; i++ {
|
||||
prefix += levelSpace
|
||||
}
|
||||
fmt.Fprintf(pw.out, prefix+format, a...)
|
||||
output := fmt.Sprintf(prefix+format, a...)
|
||||
printers.WriteEscaped(pw.out, output)
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) WriteLine(a ...interface{}) {
|
||||
fmt.Fprintln(pw.out, a...)
|
||||
output := fmt.Sprintln(a...)
|
||||
printers.WriteEscaped(pw.out, output)
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) Flush() {
|
||||
|
@ -5507,3 +5507,22 @@ func TestControllerRef(t *testing.T) {
|
||||
t.Errorf("unexpected out: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribeTerminalEscape(t *testing.T) {
|
||||
fake := fake.NewSimpleClientset(&corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mycm",
|
||||
Namespace: "foo",
|
||||
Annotations: map[string]string{"annotation1": "terminal escape: \x1b"},
|
||||
},
|
||||
})
|
||||
c := &describeClient{T: t, Namespace: "foo", Interface: fake}
|
||||
d := ConfigMapDescriber{c}
|
||||
out, err := d.Describe("foo", "mycm", DescriberSettings{ShowEvents: true})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if strings.Contains(out, "\x1b") || !strings.Contains(out, "^[") {
|
||||
t.Errorf("unexpected out: %s", out)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user