Skip to content
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

Fix Agent handling of gzipped responses #7470

Merged
merged 3 commits into from
Sep 18, 2019
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion command/agent/cache/api_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cache
import (
"context"
"fmt"
"net/http"

hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/api"
Expand Down Expand Up @@ -36,7 +37,16 @@ func (ap *APIProxy) Send(ctx context.Context, req *SendRequest) (*SendResponse,
return nil, err
}
client.SetToken(req.Token)
client.SetHeaders(req.Request.Header)

// http.Transport will transparently request gzip and decompress the response, but only if
// the client doesn't manually set the header (in which case the client has to handle
// the decompression. If gzip has already been set, remove it to avoid triggering the manual
// handling requirement.
h := clone(req.Request.Header)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why clone?

Why not use the Header built-in functions to handle removal?

Copy link
Contributor Author

@kalafut kalafut Sep 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clone b/c I didn't think this Send function should mutate the caller's request object. If that's not an issue, then we can get rid of it.

Is there a built-in to remove just a single value? https://golang.org/pkg/net/http/#Header.Del will delete all values for that header.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless the request object is being used for some other purpose down the wire I think it's fine to mutate it. I would think once it gets to this point it won't be further modified and we don't log request headers (or if we do it's before this, I would expect).

I would assume you'd have the same problem with deflate or any other encoding so I imagine you should remove them all. You could first cache them if you wanted to then use it to decide how to respond to the client, which is not a bad option, since the client is telling the cache what it will accept. But in that case you'd probably want to cache them all, not remove gzip and then cache the rest.

Copy link
Contributor Author

@kalafut kalafut Sep 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gzip is a special case because the Go std library transparently adds it depending on headers being set. Nonetheless, I think it's probably fine to just unconditionally remove the Accept-Encoding header on the request object. We're not giving up gzip to Vault since the agent's Transport will add it in anyway. I think caching can be deferred until we need to actually reprocess the response.

if h.Get("Accept-Encoding") == "gzip" {
h.Del("Accept-Encoding")
}
client.SetHeaders(h)

fwReq := client.NewRequest(req.Request.Method, req.Request.URL.Path)
fwReq.BodyBytes = req.RequestBody
Expand Down Expand Up @@ -65,3 +75,29 @@ func (ap *APIProxy) Send(ctx context.Context, req *SendRequest) (*SendResponse,
// Bubble back the api.Response as well for error checking/handling at the handler layer.
return sendResponse, err
}

// clone returns a copy of h or nil if h is nil.
//
// TODO: This is a copy of the new (Header) Clone method in Go 1.13. We should remove this once we update.
func clone(h http.Header) http.Header {
if h == nil {
return nil
}

// Find total number of values.
nv := 0
for _, vv := range h {
nv += len(vv)
}
sv := make([]string, nv) // shared backing array for headers' values
h2 := make(http.Header, len(h))

for k, vv := range h {
n := copy(sv, vv)
h2[k] = sv[:n:n]
sv = sv[n:]

}

return h2
}