mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request from GHSA-99qr-h2rp-65rc
DebugRequestMiddleware: unconditionally scrub data field
This commit is contained in:
commit
e4b9edd31f
2 changed files with 52 additions and 40 deletions
|
@ -41,7 +41,7 @@ func DebugRequestMiddleware(handler func(ctx context.Context, w http.ResponseWri
|
||||||
|
|
||||||
var postForm map[string]interface{}
|
var postForm map[string]interface{}
|
||||||
if err := json.Unmarshal(b, &postForm); err == nil {
|
if err := json.Unmarshal(b, &postForm); err == nil {
|
||||||
maskSecretKeys(postForm, r.RequestURI)
|
maskSecretKeys(postForm)
|
||||||
formStr, errMarshal := json.Marshal(postForm)
|
formStr, errMarshal := json.Marshal(postForm)
|
||||||
if errMarshal == nil {
|
if errMarshal == nil {
|
||||||
logrus.Debugf("form data: %s", string(formStr))
|
logrus.Debugf("form data: %s", string(formStr))
|
||||||
|
@ -54,41 +54,37 @@ func DebugRequestMiddleware(handler func(ctx context.Context, w http.ResponseWri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func maskSecretKeys(inp interface{}, path string) {
|
func maskSecretKeys(inp interface{}) {
|
||||||
// Remove any query string from the path
|
|
||||||
idx := strings.Index(path, "?")
|
|
||||||
if idx != -1 {
|
|
||||||
path = path[:idx]
|
|
||||||
}
|
|
||||||
// Remove trailing / characters
|
|
||||||
path = strings.TrimRight(path, "/")
|
|
||||||
|
|
||||||
if arr, ok := inp.([]interface{}); ok {
|
if arr, ok := inp.([]interface{}); ok {
|
||||||
for _, f := range arr {
|
for _, f := range arr {
|
||||||
maskSecretKeys(f, path)
|
maskSecretKeys(f)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if form, ok := inp.(map[string]interface{}); ok {
|
if form, ok := inp.(map[string]interface{}); ok {
|
||||||
|
scrub := []string{
|
||||||
|
// Note: The Data field contains the base64-encoded secret in 'secret'
|
||||||
|
// and 'config' create and update requests. Currently, no other POST
|
||||||
|
// API endpoints use a data field, so we scrub this field unconditionally.
|
||||||
|
// Change this handling to be conditional if a new endpoint is added
|
||||||
|
// in future where this field should not be scrubbed.
|
||||||
|
"data",
|
||||||
|
"jointoken",
|
||||||
|
"password",
|
||||||
|
"secret",
|
||||||
|
"signingcakey",
|
||||||
|
"unlockkey",
|
||||||
|
}
|
||||||
loop0:
|
loop0:
|
||||||
for k, v := range form {
|
for k, v := range form {
|
||||||
for _, m := range []string{"password", "secret", "jointoken", "unlockkey", "signingcakey"} {
|
for _, m := range scrub {
|
||||||
if strings.EqualFold(m, k) {
|
if strings.EqualFold(m, k) {
|
||||||
form[k] = "*****"
|
form[k] = "*****"
|
||||||
continue loop0
|
continue loop0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maskSecretKeys(v, path)
|
maskSecretKeys(v)
|
||||||
}
|
|
||||||
|
|
||||||
// Route-specific redactions
|
|
||||||
if strings.HasSuffix(path, "/secrets/create") {
|
|
||||||
for k := range form {
|
|
||||||
if k == "Data" {
|
|
||||||
form[k] = "*****"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,31 +9,25 @@ import (
|
||||||
|
|
||||||
func TestMaskSecretKeys(t *testing.T) {
|
func TestMaskSecretKeys(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
path string
|
doc string
|
||||||
input map[string]interface{}
|
input map[string]interface{}
|
||||||
expected map[string]interface{}
|
expected map[string]interface{}
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
path: "/v1.30/secrets/create",
|
doc: "secret/config create and update requests",
|
||||||
input: map[string]interface{}{"Data": "foo", "Name": "name", "Labels": map[string]interface{}{}},
|
input: map[string]interface{}{"Data": "foo", "Name": "name", "Labels": map[string]interface{}{}},
|
||||||
expected: map[string]interface{}{"Data": "*****", "Name": "name", "Labels": map[string]interface{}{}},
|
expected: map[string]interface{}{"Data": "*****", "Name": "name", "Labels": map[string]interface{}{}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/v1.30/secrets/create//",
|
doc: "masking other fields (recursively)",
|
||||||
input: map[string]interface{}{"Data": "foo", "Name": "name", "Labels": map[string]interface{}{}},
|
|
||||||
expected: map[string]interface{}{"Data": "*****", "Name": "name", "Labels": map[string]interface{}{}},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
path: "/secrets/create?key=val",
|
|
||||||
input: map[string]interface{}{"Data": "foo", "Name": "name", "Labels": map[string]interface{}{}},
|
|
||||||
expected: map[string]interface{}{"Data": "*****", "Name": "name", "Labels": map[string]interface{}{}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/v1.30/some/other/path",
|
|
||||||
input: map[string]interface{}{
|
input: map[string]interface{}{
|
||||||
"password": "pass",
|
"password": "pass",
|
||||||
|
"secret": "secret",
|
||||||
|
"jointoken": "jointoken",
|
||||||
|
"unlockkey": "unlockkey",
|
||||||
|
"signingcakey": "signingcakey",
|
||||||
"other": map[string]interface{}{
|
"other": map[string]interface{}{
|
||||||
|
"password": "pass",
|
||||||
"secret": "secret",
|
"secret": "secret",
|
||||||
"jointoken": "jointoken",
|
"jointoken": "jointoken",
|
||||||
"unlockkey": "unlockkey",
|
"unlockkey": "unlockkey",
|
||||||
|
@ -42,7 +36,12 @@ func TestMaskSecretKeys(t *testing.T) {
|
||||||
},
|
},
|
||||||
expected: map[string]interface{}{
|
expected: map[string]interface{}{
|
||||||
"password": "*****",
|
"password": "*****",
|
||||||
|
"secret": "*****",
|
||||||
|
"jointoken": "*****",
|
||||||
|
"unlockkey": "*****",
|
||||||
|
"signingcakey": "*****",
|
||||||
"other": map[string]interface{}{
|
"other": map[string]interface{}{
|
||||||
|
"password": "*****",
|
||||||
"secret": "*****",
|
"secret": "*****",
|
||||||
"jointoken": "*****",
|
"jointoken": "*****",
|
||||||
"unlockkey": "*****",
|
"unlockkey": "*****",
|
||||||
|
@ -50,10 +49,27 @@ func TestMaskSecretKeys(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
doc: "case insensitive field matching",
|
||||||
|
input: map[string]interface{}{
|
||||||
|
"PASSWORD": "pass",
|
||||||
|
"other": map[string]interface{}{
|
||||||
|
"PASSWORD": "pass",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"PASSWORD": "*****",
|
||||||
|
"other": map[string]interface{}{
|
||||||
|
"PASSWORD": "*****",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testcase := range tests {
|
for _, testcase := range tests {
|
||||||
maskSecretKeys(testcase.input, testcase.path)
|
t.Run(testcase.doc, func(t *testing.T) {
|
||||||
|
maskSecretKeys(testcase.input)
|
||||||
assert.Check(t, is.DeepEqual(testcase.expected, testcase.input))
|
assert.Check(t, is.DeepEqual(testcase.expected, testcase.input))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue