[management] record proxy version on connect (#7630)

This commit is contained in:
Pascal Fischer
2026-09-23 17:59:58 +02:00
committed by GitHub
parent cb7ca8ef3f
commit 40dffc69ae
13 changed files with 214 additions and 18 deletions
+23 -4
View File
@@ -10,6 +10,7 @@ import (
"io"
"strings"
"text/tabwriter"
"unicode"
"github.com/spf13/cobra"
@@ -68,8 +69,8 @@ func runDisconnectAll(ctx context.Context, s store.Store, out io.Writer, in io.R
toDisconnect := 0
w := tabwriter.NewWriter(out, 0, 0, 2, ' ', 0)
_, _ = fmt.Fprintln(w, "ID\tCLUSTER\tIP\tACCOUNT\tSTATUS\tLAST SEEN")
_, _ = fmt.Fprintln(w, "--\t-------\t--\t-------\t------\t---------")
_, _ = fmt.Fprintln(w, "ID\tCLUSTER\tIP\tVERSION\tACCOUNT\tSTATUS\tLAST SEEN")
_, _ = fmt.Fprintln(w, "--\t-------\t--\t-------\t-------\t------\t---------")
for _, p := range proxies {
if p.Status != rpproxy.StatusDisconnected {
@@ -80,11 +81,16 @@ func runDisconnectAll(ctx context.Context, s store.Store, out io.Writer, in io.R
if p.AccountID != nil {
account = *p.AccountID
}
version := "-"
if p.Version != "" {
version = sanitizeReportedValue(p.Version)
}
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n",
p.ID,
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
sanitizeReportedValue(p.ID),
p.ClusterAddress,
p.IPAddress,
version,
account,
p.Status,
p.LastSeen.Format("2006-01-02 15:04:05"),
@@ -139,3 +145,16 @@ func confirmDisconnectAll(out io.Writer, in io.Reader) (bool, error) {
return strings.EqualFold(strings.TrimSpace(scanner.Text()), disconnectAllConfirmation), nil
}
// sanitizeReportedValue replaces non-printable characters in a value the proxy
// reports about itself. Both the id and the version arrive unvalidated over
// gRPC, so a tab would forge a column, a carriage return or ANSI escape would
// redraw the operator's terminal, and U+202E would reverse the rest of the line.
func sanitizeReportedValue(s string) string {
return strings.Map(func(r rune) rune {
if unicode.IsPrint(r) {
return r
}
return '\uFFFD'
}, s)
}
+39
View File
@@ -35,6 +35,7 @@ func seedProxies(t *testing.T, ctx context.Context, s store.Store) {
SessionID: "session-1",
ClusterAddress: "cluster-a.example.com",
IPAddress: "10.0.0.1",
Version: "0.60.0",
LastSeen: time.Now(),
Status: rpproxy.StatusConnected,
},
@@ -89,6 +90,7 @@ func TestRunDisconnectAllWithConfirmation(t *testing.T) {
require.Contains(t, output, "proxy-2")
require.Contains(t, output, "proxy-3")
require.Contains(t, output, "cluster-a.example.com")
require.Contains(t, output, "0.60.0")
require.Contains(t, output, "account-1")
require.Contains(t, output, "Type \"disconnect all proxies\" to continue")
require.Contains(t, output, "Force-marked 2 of 3 reverse proxy instance(s) as disconnected.")
@@ -178,3 +180,40 @@ func TestRunDisconnectAllEmpty(t *testing.T) {
require.NoError(t, runDisconnectAll(ctx, s, &out, strings.NewReader(""), false, false))
require.Contains(t, out.String(), "No reverse proxy instances found.")
}
func TestRunDisconnectAllEscapesProxyReportedFields(t *testing.T) {
ctx := context.Background()
s := newTestStore(t)
// A proxy reports its own id and version on connect, so both reach this
// listing unvalidated. Carriage returns, tabs and ANSI escapes would let
// a malicious proxy redraw the table or forge a row on the operator's
// terminal; U+202E would reverse the rendering of the rest of the line.
require.NoError(t, s.SaveProxy(ctx, &rpproxy.Proxy{
ID: "proxy-\r\x1b[2Kevil",
SessionID: "session-1",
ClusterAddress: "cluster-a.example.com",
IPAddress: "10.0.0.1",
Version: "0.60.0\tfake\rcolumn\u202e",
LastSeen: time.Now(),
Status: rpproxy.StatusConnected,
}))
var out bytes.Buffer
require.NoError(t, runDisconnectAll(ctx, s, &out, strings.NewReader(disconnectAllConfirmation+"\n"), true, false))
output := out.String()
for _, forbidden := range []string{"\r", "\x1b", "\u202e"} {
require.NotContains(t, output, forbidden, "listing must not carry proxy-reported control characters")
}
// The table has one data row; a smuggled tab would add a phantom column.
var dataRow string
for _, line := range strings.Split(output, "\n") {
if strings.Contains(line, "evil") {
dataRow = line
}
}
require.NotEmpty(t, dataRow, "listing should still show the proxy row")
require.NotContains(t, dataRow, "\t", "tabwriter output should not carry a smuggled column separator")
require.Contains(t, dataRow, "0.60.0", "the printable part of the version should survive")
}