feat: include `crypto` diagnostics in `/debug/vars` output (#23948)
* feat: include `crypto` diagnostics in `/debug/vars` output Pulls `crypto` diagnostics and includes them in `/debug/vars` output. If no `crypto` diagnostics are available, then OSS crypto information will be shown instead. closes: #23947pull/23970/head v1.11.0
parent
e484c4d871
commit
ac350884d7
|
@ -44,6 +44,8 @@ import (
|
|||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var ErrDiagnosticsValueMissing = errors.New("expected diagnostic value missing")
|
||||
|
||||
const (
|
||||
// DefaultChunkSize specifies the maximum number of points that will
|
||||
// be read before sending results back to the engine.
|
||||
|
@ -2249,6 +2251,40 @@ func (h *Handler) serveExpvar(w http.ResponseWriter, r *http.Request) {
|
|||
first = false
|
||||
fmt.Fprintf(w, "\"cmdline\": %s", val)
|
||||
}
|
||||
|
||||
// We're going to print some kind of crypto data, we just
|
||||
// need to find the proper source for it.
|
||||
{
|
||||
var jv map[string]interface{}
|
||||
val := diags["crypto"]
|
||||
if val != nil {
|
||||
jv, err = parseCryptoDiagnostics(val)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrDiagnosticsValueMissing) {
|
||||
// log missing values, but don't error out
|
||||
h.Logger.Warn(err.Error())
|
||||
} else {
|
||||
h.httpError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
jv = ossCryptoDiagnostics()
|
||||
}
|
||||
|
||||
data, err := json.Marshal(jv)
|
||||
if err != nil {
|
||||
h.httpError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !first {
|
||||
fmt.Fprintln(w, ",")
|
||||
}
|
||||
first = false
|
||||
fmt.Fprintf(w, "\"crypto\": %s", data)
|
||||
}
|
||||
|
||||
if val := expvar.Get("memstats"); val != nil {
|
||||
if !first {
|
||||
fmt.Fprintln(w, ",")
|
||||
|
@ -2433,6 +2469,55 @@ func parseBuildInfo(d *diagnostics.Diagnostics) (map[string]interface{}, error)
|
|||
return m, nil
|
||||
}
|
||||
|
||||
// ossCryptoDiagnostics creates a default crypto diagnostics map that
|
||||
// can be marshaled into JSON for /debug/vars.
|
||||
func ossCryptoDiagnostics() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"ensureFIPS": false,
|
||||
"FIPS": false,
|
||||
"implementation": "Go",
|
||||
"passwordHash": "bcrypt",
|
||||
}
|
||||
}
|
||||
|
||||
// parseCryptoDiagnostics converts the crypto diagnostics into an appropriate
|
||||
// format for marshaling to JSON in the /debug/vars format.
|
||||
func parseCryptoDiagnostics(d *diagnostics.Diagnostics) (map[string]interface{}, error) {
|
||||
// We use ossCryptoDiagnostics as a template for columns we need to pull from d.
|
||||
// If the column is missing from d, we will nil out the value in m to avoid lying
|
||||
// about a value to the user and making troubleshooting harder.
|
||||
m := ossCryptoDiagnostics()
|
||||
var missing []string
|
||||
|
||||
for key := range m {
|
||||
// Find the associated column.
|
||||
ci := -1
|
||||
for i, col := range d.Columns {
|
||||
if col == key {
|
||||
ci = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Don't error out if we can't find the column or cell for a given key, just nil
|
||||
// out the value in m. There could still be other useful information we gather.
|
||||
// Column not found or data cell not found
|
||||
if ci == -1 || len(d.Rows) < 1 || len(d.Rows[0]) <= ci {
|
||||
m[key] = nil
|
||||
missing = append(missing, key)
|
||||
continue
|
||||
}
|
||||
|
||||
m[key] = d.Rows[0][ci]
|
||||
}
|
||||
|
||||
if len(missing) > 0 {
|
||||
// If you're getting this error, you probably need to update enterprise.
|
||||
return m, fmt.Errorf("parseCryptoDiagnostics: missing %s: %w", strings.Join(missing, ","), ErrDiagnosticsValueMissing)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// httpError writes an error to the client in a standard format.
|
||||
func (h *Handler) httpError(w http.ResponseWriter, errmsg string, code int) {
|
||||
if code == http.StatusUnauthorized {
|
||||
|
|
|
@ -2616,6 +2616,12 @@ func TestHandlerDebugVars(t *testing.T) {
|
|||
return res
|
||||
}
|
||||
|
||||
newDiagFn := func(d map[string]*diagnostics.Diagnostics) func() (map[string]*diagnostics.Diagnostics, error) {
|
||||
return func() (map[string]*diagnostics.Diagnostics, error) {
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
|
||||
var Ignored = []string{"memstats", "cmdline"}
|
||||
read := func(t *testing.T, b *bytes.Buffer, del ...string) map[string]interface{} {
|
||||
t.Helper()
|
||||
|
@ -2652,17 +2658,19 @@ func TestHandlerDebugVars(t *testing.T) {
|
|||
stat("shard", tags("path", "/mnt/foo", "id", "111"), nil),
|
||||
)
|
||||
}
|
||||
h.Monitor.DiagnosticsFn = newDiagFn(map[string]*diagnostics.Diagnostics{})
|
||||
req := MustNewRequest("GET", "/debug/vars", nil)
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, req)
|
||||
got := keys(read(t, w.Body, Ignored...))
|
||||
exp := []string{"database:foo", "hh:/mnt/foo/bar", "httpd:https:127.0.0.1:8088", "other", "shard:/mnt/foo:111"}
|
||||
exp := []string{"crypto", "database:foo", "hh:/mnt/foo/bar", "httpd:https:127.0.0.1:8088", "other", "shard:/mnt/foo:111"}
|
||||
if !cmp.Equal(got, exp) {
|
||||
t.Errorf("unexpected keys; -got/+exp\n%s", cmp.Diff(got, exp))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("generates numbered keys for collisions", func(t *testing.T) {
|
||||
// This also implicitly tests the case where no `crypto` diagnostics are not set by application.
|
||||
h := NewHandler(false)
|
||||
h.Monitor.StatisticsFn = func(_ map[string]string) ([]*monitor.Statistic, error) {
|
||||
return stats(
|
||||
|
@ -2677,6 +2685,12 @@ func TestHandlerDebugVars(t *testing.T) {
|
|||
h.ServeHTTP(w, req)
|
||||
got := read(t, w.Body, Ignored...)
|
||||
exp := map[string]interface{}{
|
||||
"crypto": map[string]interface{}{
|
||||
"FIPS": false,
|
||||
"ensureFIPS": false,
|
||||
"passwordHash": "bcrypt",
|
||||
"implementation": "Go",
|
||||
},
|
||||
"hh_processor": map[string]interface{}{
|
||||
"name": "hh_processor",
|
||||
"tags": map[string]interface{}{"db": "foo", "shardID": "10"},
|
||||
|
@ -2703,6 +2717,35 @@ func TestHandlerDebugVars(t *testing.T) {
|
|||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("checks crypto diagnostic handling", func(t *testing.T) {
|
||||
h := NewHandler(false)
|
||||
// intentionally leave out "ensureFIPS" to test that code path
|
||||
h.Monitor.DiagnosticsFn = newDiagFn(
|
||||
map[string]*diagnostics.Diagnostics{
|
||||
"crypto": diagnostics.RowFromMap(map[string]interface{}{
|
||||
"FIPS": true,
|
||||
"passwordHash": "pbkdf2-sha256",
|
||||
"implementation": "BoringCrypto",
|
||||
}),
|
||||
})
|
||||
req := MustNewRequest("GET", "/debug/vars", nil)
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, req)
|
||||
got := read(t, w.Body, Ignored...)
|
||||
exp := map[string]interface{}{
|
||||
"crypto": map[string]interface{}{
|
||||
"FIPS": true,
|
||||
"ensureFIPS": nil,
|
||||
"passwordHash": "pbkdf2-sha256",
|
||||
"implementation": "BoringCrypto",
|
||||
},
|
||||
}
|
||||
if !cmp.Equal(got, exp) {
|
||||
t.Errorf("unexpected keys; -got/+exp\n%s", cmp.Diff(got, exp))
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// NewHandler represents a test wrapper for httpd.Handler.
|
||||
|
|
Loading…
Reference in New Issue