-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: include crypto
diagnostics in /debug/vars
output
#23948
Changes from all commits
3f0e5e1
6785fa5
94f215b
28eba8f
af43e11
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
|
@@ -872,7 +874,6 @@ func (h *Handler) async(q *influxql.Query, results <-chan *query.Result) { | |
// in the database URL query value. It is encoded using a forward slash like | ||
// "database/retentionpolicy" and we should be able to simply split that string | ||
// on the forward slash. | ||
// | ||
func bucket2dbrp(bucket string) (string, string, error) { | ||
// test for a slash in our bucket name. | ||
switch idx := strings.IndexByte(bucket, '/'); idx { | ||
|
@@ -2250,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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same, let's log There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll go a step further and say the fact there are There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Absolutely agree. But I didn't want to generate more work for you rewriting everything. |
||
} | ||
|
||
if val := expvar.Get("memstats"); val != nil { | ||
if !first { | ||
fmt.Fprintln(w, ",") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same, let's log |
||
|
@@ -2434,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 { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you want to omit this entirely, rather than having a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Assumptions:
Option 1: If enterprise doesn't provide an attribute value, Option 2: Leave the field out if enterprise doesn't set it. Option 3: Missing fields are given a |
||
"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. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still in favor of an
h.Logger.Error(err)
ifFprintln
fails here.