mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-01 11:09:15 +02:00
[management] record proxy version on connect (#7630)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user