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: #23947
pull/23970/head v1.11.0
Geoffrey Wossum 2022-12-06 12:02:30 -06:00 committed by GitHub
parent e484c4d871
commit ac350884d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 129 additions and 1 deletions

View File

@ -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 {

View File

@ -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.