From 92b3e95016ab9ef5ba85d0b15595c4cd84ba2d9b Mon Sep 17 00:00:00 2001 From: hamid ghaf Date: Wed, 17 Nov 2021 23:47:22 -0800 Subject: [PATCH 1/5] Unify NewHTTPResponseWriter ant NewStatusHeaderResponseWriter to fix ResponseWriter issues --- http/handler.go | 111 +++------------------- http/logical.go | 2 +- physical/raft/raft.go | 2 +- physical/raft/snapshot_test.go | 2 +- sdk/logical/request.go | 7 +- sdk/logical/response.go | 132 ++++++++++++++++++++++---- vault/custom_response_headers.go | 17 ++-- vault/custom_response_headers_test.go | 2 +- vault/logical_system.go | 2 +- 9 files changed, 144 insertions(+), 133 deletions(-) diff --git a/http/handler.go b/http/handler.go index 65b6094c31d4..1e29fa41192e 100644 --- a/http/handler.go +++ b/http/handler.go @@ -17,14 +17,12 @@ import ( "net/url" "os" "regexp" - "strconv" "strings" "time" "github.com/NYTimes/gziphandler" "github.com/hashicorp/errwrap" "github.com/hashicorp/go-cleanhttp" - log "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-secure-stdlib/parseutil" "github.com/hashicorp/go-sockaddr" "github.com/hashicorp/vault/helper/namespace" @@ -215,89 +213,6 @@ func Handler(props *vault.HandlerProperties) http.Handler { return printablePathCheckHandler } -type WrappingResponseWriter interface { - http.ResponseWriter - Wrapped() http.ResponseWriter -} - -type statusHeaderResponseWriter struct { - wrapped http.ResponseWriter - logger log.Logger - wroteHeader bool - statusCode int - headers map[string][]*vault.CustomHeader -} - -func (w *statusHeaderResponseWriter) Wrapped() http.ResponseWriter { - return w.wrapped -} - -func (w *statusHeaderResponseWriter) Header() http.Header { - return w.wrapped.Header() -} - -func (w *statusHeaderResponseWriter) Write(buf []byte) (int, error) { - // It is allowed to only call ResponseWriter.Write and skip - // ResponseWriter.WriteHeader. An example of such a situation is - // "handleUIStub". The Write function will internally set the status code - // 200 for the response for which that call might invoke other - // implementations of the WriteHeader function. So, we still need to set - // the custom headers. In cases where both WriteHeader and Write of - // statusHeaderResponseWriter struct are called the internal call to the - // WriterHeader invoked from inside Write method won't change the headers. - if !w.wroteHeader { - w.setCustomResponseHeaders(w.statusCode) - } - - return w.wrapped.Write(buf) -} - -func (w *statusHeaderResponseWriter) WriteHeader(statusCode int) { - w.setCustomResponseHeaders(statusCode) - w.wrapped.WriteHeader(statusCode) - w.statusCode = statusCode - // in cases where Write is called after WriteHeader, let's prevent setting - // ResponseWriter headers twice - w.wroteHeader = true -} - -func (w *statusHeaderResponseWriter) setCustomResponseHeaders(status int) { - sch := w.headers - if sch == nil { - w.logger.Warn("status code header map not configured") - return - } - - // Checking the validity of the status code - if status >= 600 || status < 100 { - return - } - - // setter function to set the headers - setter := func(hvl []*vault.CustomHeader) { - for _, hv := range hvl { - w.Header().Set(hv.Name, hv.Value) - } - } - - // Setting the default headers first - setter(sch["default"]) - - // setting the Xyy pattern first - d := fmt.Sprintf("%vxx", status/100) - if val, ok := sch[d]; ok { - setter(val) - } - - // Setting the specific headers - if val, ok := sch[strconv.Itoa(status)]; ok { - setter(val) - } - - return -} - -var _ WrappingResponseWriter = &statusHeaderResponseWriter{} type copyResponseWriter struct { wrapped http.ResponseWriter @@ -389,25 +304,21 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr hostname, _ := os.Hostname() return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + nw := logical.NewStatusHeaderResponseWriter(w) // This block needs to be here so that upon sending SIGHUP, custom response // headers are also reloaded into the handlers. if props.ListenerConfig != nil { la := props.ListenerConfig.Address listenerCustomHeaders := core.GetListenerCustomResponseHeaders(la) if listenerCustomHeaders != nil { - w = &statusHeaderResponseWriter{ - wrapped: w, - logger: core.Logger(), - wroteHeader: false, - statusCode: 200, - headers: listenerCustomHeaders.StatusCodeHeaderMap, - } + nw.SetHeaders(listenerCustomHeaders.StatusCodeHeaderMap) } } // Set the Cache-Control header for all the responses returned // by Vault - w.Header().Set("Cache-Control", "no-store") + nw.Header().Set("Cache-Control", "no-store") // Start with the request context ctx := r.Context() @@ -431,19 +342,19 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr if core.RaftNodeIDHeaderEnabled() { nodeID := core.GetRaftNodeID() if nodeID != "" { - w.Header().Set("X-Vault-Raft-Node-ID", nodeID) + nw.Header().Set("X-Vault-Raft-Node-ID", nodeID) } } if core.HostnameHeaderEnabled() && hostname != "" { - w.Header().Set("X-Vault-Hostname", hostname) + nw.Header().Set("X-Vault-Hostname", hostname) } switch { case strings.HasPrefix(r.URL.Path, "/v1/"): newR, status := adjustRequest(core, r) if status != 0 { - respondError(w, status, nil) + respondError(nw, status, nil) cancelFunc() return } @@ -451,7 +362,7 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr case strings.HasPrefix(r.URL.Path, "/ui"), r.URL.Path == "/robots.txt", r.URL.Path == "/": default: - respondError(w, http.StatusNotFound, nil) + respondError(nw, http.StatusNotFound, nil) cancelFunc() return } @@ -459,10 +370,10 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr // Setting the namespace in the header to be included in the error message ns := r.Header.Get(consts.NamespaceHeaderName) if ns != "" { - w.Header().Set(consts.NamespaceHeaderName, ns) + nw.Header().Set(consts.NamespaceHeaderName, ns) } - h.ServeHTTP(w, r) + h.ServeHTTP(nw, r) cancelFunc() return @@ -742,7 +653,7 @@ func parseJSONRequest(perfStandby bool, r *http.Request, w http.ResponseWriter, // requestTooLarger. So we let it have access to the underlying // ResponseWriter. inw := w - if myw, ok := inw.(WrappingResponseWriter); ok { + if myw, ok := inw.(logical.WrappingResponseWriter); ok { inw = myw.Wrapped() } reader = http.MaxBytesReader(inw, r.Body, max) diff --git a/http/logical.go b/http/logical.go index a7730033b351..4b8b52a397fa 100644 --- a/http/logical.go +++ b/http/logical.go @@ -199,7 +199,7 @@ func buildLogicalRequestNoAuth(perfStandby bool, w http.ResponseWriter, r *http. req.HTTPRequest = r } if responseWriter != nil { - req.ResponseWriter = logical.NewHTTPResponseWriter(responseWriter) + req.ResponseWriter = logical.NewStatusHeaderResponseWriter(responseWriter) } return req, origBody, 0, nil diff --git a/physical/raft/raft.go b/physical/raft/raft.go index 47b96d7c0dcb..8dc261524f6d 100644 --- a/physical/raft/raft.go +++ b/physical/raft/raft.go @@ -1138,7 +1138,7 @@ func (b *RaftBackend) Peers(ctx context.Context) ([]Peer, error) { // SnapshotHTTP is a wrapper for Snapshot that sends the snapshot as an HTTP // response. -func (b *RaftBackend) SnapshotHTTP(out *logical.HTTPResponseWriter, access *seal.Access) error { +func (b *RaftBackend) SnapshotHTTP(out *logical.StatusHeaderResponseWriter, access *seal.Access) error { out.Header().Add("Content-Disposition", "attachment") out.Header().Add("Content-Type", "application/gzip") diff --git a/physical/raft/snapshot_test.go b/physical/raft/snapshot_test.go index 53757c5683d7..43173a1975b8 100644 --- a/physical/raft/snapshot_test.go +++ b/physical/raft/snapshot_test.go @@ -474,7 +474,7 @@ func TestRaft_Snapshot_Take_Restore(t *testing.T) { } recorder := httptest.NewRecorder() - snap := logical.NewHTTPResponseWriter(recorder) + snap := logical.NewStatusHeaderResponseWriter(recorder) err := raft1.Snapshot(snap, nil) if err != nil { diff --git a/sdk/logical/request.go b/sdk/logical/request.go index 829c155fd095..26880b7a8553 100644 --- a/sdk/logical/request.go +++ b/sdk/logical/request.go @@ -203,7 +203,7 @@ type Request struct { // ResponseWriter if set can be used to stream a response value to the http // request that generated this logical.Request object. - ResponseWriter *HTTPResponseWriter `json:"-" sentinel:""` + ResponseWriter *StatusHeaderResponseWriter `json:"-" sentinel:""` // requiredState is used internally to propagate the X-Vault-Index request // header to later levels of request processing that operate only on @@ -377,3 +377,8 @@ type InitializationRequest struct { // Storage can be used to durably store and retrieve state. Storage Storage } + +type CustomHeader struct { + Name string + Value string +} diff --git a/sdk/logical/response.go b/sdk/logical/response.go index a6751125394b..f64d99489918 100644 --- a/sdk/logical/response.go +++ b/sdk/logical/response.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "sync/atomic" "github.com/hashicorp/vault/sdk/helper/wrapping" @@ -192,30 +193,127 @@ func RespondWithStatusCode(resp *Response, req *Request, code int) (*Response, e return ret, nil } -// HTTPResponseWriter is optionally added to a request object and can be used to -// write directly to the HTTP response writer. -type HTTPResponseWriter struct { +type WrappingResponseWriter interface { http.ResponseWriter - written *uint32 + Wrapped() http.ResponseWriter + SetHeaders(h map[string][]*CustomHeader) + GetHeaders() map[string][]*CustomHeader + StatusCode() int } -// NewHTTPResponseWriter creates a new HTTPResponseWriter object that wraps the -// provided io.Writer. -func NewHTTPResponseWriter(w http.ResponseWriter) *HTTPResponseWriter { - return &HTTPResponseWriter{ - ResponseWriter: w, - written: new(uint32), +type StatusHeaderResponseWriter struct { + wrapped http.ResponseWriter + wroteHeader bool + statusCode int + headers map[string][]*CustomHeader + written *uint32 +} + +func NewStatusHeaderResponseWriter (w http.ResponseWriter) *StatusHeaderResponseWriter { + nw, ok := w.(WrappingResponseWriter) + if ok { + return &StatusHeaderResponseWriter{ + wrapped: nw.Wrapped(), + wroteHeader: false, + statusCode: 200, + headers: nw.GetHeaders(), + written: new(uint32), + } + } else { + return &StatusHeaderResponseWriter{ + wrapped: w, + wroteHeader: false, + statusCode: 200, + headers: nil, + written: new(uint32), + } } } -// Write will write the bytes to the underlying io.Writer. -func (rw *HTTPResponseWriter) Write(bytes []byte) (int, error) { - atomic.StoreUint32(rw.written, 1) +// Written tells us if the writer has been written to yet. +func (w *StatusHeaderResponseWriter) Written() bool { + return atomic.LoadUint32(w.written) == 1 +} - return rw.ResponseWriter.Write(bytes) +func (w *StatusHeaderResponseWriter) Wrapped() http.ResponseWriter { + return w.wrapped } -// Written tells us if the writer has been written to yet. -func (rw *HTTPResponseWriter) Written() bool { - return atomic.LoadUint32(rw.written) == 1 +func (w *StatusHeaderResponseWriter) GetHeaders() map[string][]*CustomHeader { + return w.headers +} + +func (w *StatusHeaderResponseWriter) SetHeaders(h map[string][]*CustomHeader) { + w.headers = h +} + +func (w *StatusHeaderResponseWriter) StatusCode() int { + return w.statusCode +} + +func (w *StatusHeaderResponseWriter) Header() http.Header { + return w.wrapped.Header() +} + +func (w *StatusHeaderResponseWriter) Write(buf []byte) (int, error) { + // It is allowed to only call ResponseWriter.Write and skip + // ResponseWriter.WriteHeader. An example of such a situation is + // "handleUIStub". The Write function will internally set the status code + // 200 for the response for which that call might invoke other + // implementations of the WriteHeader function. So, we still need to set + // the custom headers. In cases where both WriteHeader and Write of + // statusHeaderResponseWriter struct are called the internal call to the + // WriterHeader invoked from inside Write method won't change the headers. + if !w.wroteHeader { + w.setCustomResponseHeaders(w.statusCode) + } + atomic.StoreUint32(w.written, 1) + + return w.wrapped.Write(buf) } + +func (w *StatusHeaderResponseWriter) WriteHeader(statusCode int) { + w.setCustomResponseHeaders(statusCode) + w.wrapped.WriteHeader(statusCode) + w.statusCode = statusCode + // in cases where Write is called after WriteHeader, let's prevent setting + // ResponseWriter headers twice + w.wroteHeader = true +} + +func (w *StatusHeaderResponseWriter) setCustomResponseHeaders(status int) { + sch := w.headers + if sch == nil { + return + } + + // Checking the validity of the status code + if status >= 600 || status < 100 { + return + } + + // setter function to set the headers + setter := func(hvl []*CustomHeader) { + for _, hv := range hvl { + w.Header().Set(hv.Name, hv.Value) + } + } + + // Setting the default headers first + setter(sch["default"]) + + // setting the Xyy pattern first + d := fmt.Sprintf("%vxx", status/100) + if val, ok := sch[d]; ok { + setter(val) + } + + // Setting the specific headers + if val, ok := sch[strconv.Itoa(status)]; ok { + setter(val) + } + + return +} + +var _ WrappingResponseWriter = &StatusHeaderResponseWriter{} diff --git a/vault/custom_response_headers.go b/vault/custom_response_headers.go index 54df089547fc..3d4244a91de9 100644 --- a/vault/custom_response_headers.go +++ b/vault/custom_response_headers.go @@ -1,27 +1,24 @@ package vault import ( + "fmt" "net/http" "net/textproto" "strings" log "github.com/hashicorp/go-hclog" "github.com/hashicorp/vault/internalshared/configutil" + "github.com/hashicorp/vault/sdk/logical" ) type ListenerCustomHeaders struct { Address string - StatusCodeHeaderMap map[string][]*CustomHeader + StatusCodeHeaderMap map[string][]*logical.CustomHeader // ConfiguredHeadersStatusCodeMap field is introduced so that we would not need to loop through // StatusCodeHeaderMap to see if a header exists, the key for this map is the headers names configuredHeadersStatusCodeMap map[string][]string } -type CustomHeader struct { - Name string - Value string -} - func NewListenerCustomHeader(ln []*configutil.Listener, logger log.Logger, uiHeaders http.Header) []*ListenerCustomHeaders { var listenerCustomHeadersList []*ListenerCustomHeaders @@ -29,10 +26,10 @@ func NewListenerCustomHeader(ln []*configutil.Listener, logger log.Logger, uiHea listenerCustomHeaderStruct := &ListenerCustomHeaders{ Address: l.Address, } - listenerCustomHeaderStruct.StatusCodeHeaderMap = make(map[string][]*CustomHeader) + listenerCustomHeaderStruct.StatusCodeHeaderMap = make(map[string][]*logical.CustomHeader) listenerCustomHeaderStruct.configuredHeadersStatusCodeMap = make(map[string][]string) for statusCode, headerValMap := range l.CustomResponseHeaders { - var customHeaderList []*CustomHeader + var customHeaderList []*logical.CustomHeader for headerName, headerVal := range headerValMap { // Sanitizing custom headers // X-Vault- prefix is reserved for Vault internal processes @@ -45,7 +42,7 @@ func NewListenerCustomHeader(ln []*configutil.Listener, logger log.Logger, uiHea if uiHeaders != nil { exist := uiHeaders.Get(headerName) if exist != "" { - logger.Warn("found a duplicate header in UI", "header:", headerName, "Headers defined in the server configuration take precedence.") + logger.Warn(fmt.Sprintf("found a duplicate header in UI: header=%s. Headers defined in the server configuration take precedence.", headerName)) } } @@ -55,7 +52,7 @@ func NewListenerCustomHeader(ln []*configutil.Listener, logger log.Logger, uiHea continue } - ch := &CustomHeader{ + ch := &logical.CustomHeader{ Name: headerName, Value: headerVal, } diff --git a/vault/custom_response_headers_test.go b/vault/custom_response_headers_test.go index 4b370a943f42..dabd4ef23fae 100644 --- a/vault/custom_response_headers_test.go +++ b/vault/custom_response_headers_test.go @@ -128,7 +128,7 @@ func TestCustomResponseHeadersConfigInteractUiConfig(t *testing.T) { } w := httptest.NewRecorder() - hw := logical.NewHTTPResponseWriter(w) + hw := logical.NewStatusHeaderResponseWriter(w) // setting a header that already exist in custom headers req := logical.TestRequest(t, logical.UpdateOperation, "config/ui/headers/X-Custom-Header") diff --git a/vault/logical_system.go b/vault/logical_system.go index 6f99f6b17a92..bd602f21368c 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -2983,7 +2983,7 @@ func (b *SystemBackend) handleMonitor(ctx context.Context, req *logical.Request, return logical.ErrorResponse("unknown log level"), nil } - flusher, ok := w.ResponseWriter.(http.Flusher) + flusher, ok := w.Wrapped().(http.Flusher) if !ok { return logical.ErrorResponse("streaming not supported"), nil } From edb878aedca3b44ad1193dc741452e24e9ec3db4 Mon Sep 17 00:00:00 2001 From: hamid ghaf Date: Wed, 17 Nov 2021 23:56:55 -0800 Subject: [PATCH 2/5] adding changelog --- changelog/13200.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/13200.txt diff --git a/changelog/13200.txt b/changelog/13200.txt new file mode 100644 index 000000000000..db5a8e8767ad --- /dev/null +++ b/changelog/13200.txt @@ -0,0 +1,3 @@ +```release-note:bug +http:Unify HTTPResponseWriter and StatusHeaderResponseWriter +``` From 89a5b472b5d5ad6a4ffd0b4fe77ac5f62274379e Mon Sep 17 00:00:00 2001 From: hamid ghaf Date: Fri, 19 Nov 2021 08:03:31 -0800 Subject: [PATCH 3/5] removing unnecessary function from the WrappingResponseWriter interface --- sdk/logical/response.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/sdk/logical/response.go b/sdk/logical/response.go index f64d99489918..e90fd680bbad 100644 --- a/sdk/logical/response.go +++ b/sdk/logical/response.go @@ -196,9 +196,7 @@ func RespondWithStatusCode(resp *Response, req *Request, code int) (*Response, e type WrappingResponseWriter interface { http.ResponseWriter Wrapped() http.ResponseWriter - SetHeaders(h map[string][]*CustomHeader) GetHeaders() map[string][]*CustomHeader - StatusCode() int } type StatusHeaderResponseWriter struct { @@ -247,10 +245,6 @@ func (w *StatusHeaderResponseWriter) SetHeaders(h map[string][]*CustomHeader) { w.headers = h } -func (w *StatusHeaderResponseWriter) StatusCode() int { - return w.statusCode -} - func (w *StatusHeaderResponseWriter) Header() http.Header { return w.wrapped.Header() } From 6ea1b9093acc013278c04119738bd172dd65805d Mon Sep 17 00:00:00 2001 From: hamid ghaf Date: Mon, 22 Nov 2021 14:27:23 -0800 Subject: [PATCH 4/5] changing logical requests responseWriter type --- http/handler.go | 7 +++++-- http/logical.go | 9 ++++++--- physical/raft/raft.go | 4 ++-- physical/raft/snapshot_test.go | 6 ++---- sdk/logical/request.go | 2 +- sdk/logical/response.go | 29 +++++++-------------------- vault/custom_response_headers_test.go | 6 ++++-- vault/logical_system.go | 15 +++++++++++--- vault/logical_system_pprof.go | 22 ++++++++++---------- vault/logical_system_raft.go | 2 +- 10 files changed, 51 insertions(+), 51 deletions(-) diff --git a/http/handler.go b/http/handler.go index 1e29fa41192e..75b358f9ffcd 100644 --- a/http/handler.go +++ b/http/handler.go @@ -906,8 +906,11 @@ func request(core *vault.Core, w http.ResponseWriter, rawReq *http.Request, r *l // additional output. Headers have already been sent. If the response writer // is set but has not been written to it likely means there was some kind of // error - if r.ResponseWriter != nil && r.ResponseWriter.Written() { - return nil, true, false + if r.ResponseWriter != nil { + w, ok := (*r.ResponseWriter).(logical.WrappingResponseWriter) + if ok && w.Written() { + return nil, true, false + } } if respondErrorCommon(w, r, resp, err) { diff --git a/http/logical.go b/http/logical.go index 4b8b52a397fa..eee51a298048 100644 --- a/http/logical.go +++ b/http/logical.go @@ -199,7 +199,7 @@ func buildLogicalRequestNoAuth(perfStandby bool, w http.ResponseWriter, r *http. req.HTTPRequest = r } if responseWriter != nil { - req.ResponseWriter = logical.NewStatusHeaderResponseWriter(responseWriter) + req.ResponseWriter = &responseWriter } return req, origBody, 0, nil @@ -368,8 +368,11 @@ func respondLogical(core *vault.Core, w http.ResponseWriter, r *http.Request, re // If vault's core has already written to the response writer do not add any // additional output. Headers have already been sent. - if req != nil && req.ResponseWriter != nil && req.ResponseWriter.Written() { - return + if req != nil && req.ResponseWriter != nil { + w, ok := (*req.ResponseWriter).(logical.WrappingResponseWriter) + if ok && w.Written() { + return + } } if resp != nil { diff --git a/physical/raft/raft.go b/physical/raft/raft.go index 8dc261524f6d..699a47c356fe 100644 --- a/physical/raft/raft.go +++ b/physical/raft/raft.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http" "os" "path/filepath" "strconv" @@ -27,7 +28,6 @@ import ( "github.com/hashicorp/vault/helper/metricsutil" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/jsonutil" - "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/physical" "github.com/hashicorp/vault/vault/cluster" "github.com/hashicorp/vault/vault/seal" @@ -1138,7 +1138,7 @@ func (b *RaftBackend) Peers(ctx context.Context) ([]Peer, error) { // SnapshotHTTP is a wrapper for Snapshot that sends the snapshot as an HTTP // response. -func (b *RaftBackend) SnapshotHTTP(out *logical.StatusHeaderResponseWriter, access *seal.Access) error { +func (b *RaftBackend) SnapshotHTTP(out http.ResponseWriter, access *seal.Access) error { out.Header().Add("Content-Disposition", "attachment") out.Header().Add("Content-Type", "application/gzip") diff --git a/physical/raft/snapshot_test.go b/physical/raft/snapshot_test.go index 43173a1975b8..9d547bfadae9 100644 --- a/physical/raft/snapshot_test.go +++ b/physical/raft/snapshot_test.go @@ -17,7 +17,6 @@ import ( hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/raft" - "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/physical" "github.com/hashicorp/vault/sdk/plugin/pb" ) @@ -473,8 +472,7 @@ func TestRaft_Snapshot_Take_Restore(t *testing.T) { } } - recorder := httptest.NewRecorder() - snap := logical.NewStatusHeaderResponseWriter(recorder) + snap := httptest.NewRecorder() err := raft1.Snapshot(snap, nil) if err != nil { @@ -492,7 +490,7 @@ func TestRaft_Snapshot_Take_Restore(t *testing.T) { } } - snapFile, cleanup, metadata, err := raft1.WriteSnapshotToTemp(ioutil.NopCloser(recorder.Body), nil) + snapFile, cleanup, metadata, err := raft1.WriteSnapshotToTemp(ioutil.NopCloser(snap.Body), nil) if err != nil { t.Fatal(err) } diff --git a/sdk/logical/request.go b/sdk/logical/request.go index 26880b7a8553..08cedc447163 100644 --- a/sdk/logical/request.go +++ b/sdk/logical/request.go @@ -203,7 +203,7 @@ type Request struct { // ResponseWriter if set can be used to stream a response value to the http // request that generated this logical.Request object. - ResponseWriter *StatusHeaderResponseWriter `json:"-" sentinel:""` + ResponseWriter *http.ResponseWriter `json:"-" sentinel:""` // requiredState is used internally to propagate the X-Vault-Index request // header to later levels of request processing that operate only on diff --git a/sdk/logical/response.go b/sdk/logical/response.go index e90fd680bbad..353dc50f72b0 100644 --- a/sdk/logical/response.go +++ b/sdk/logical/response.go @@ -196,7 +196,7 @@ func RespondWithStatusCode(resp *Response, req *Request, code int) (*Response, e type WrappingResponseWriter interface { http.ResponseWriter Wrapped() http.ResponseWriter - GetHeaders() map[string][]*CustomHeader + Written() bool } type StatusHeaderResponseWriter struct { @@ -208,23 +208,12 @@ type StatusHeaderResponseWriter struct { } func NewStatusHeaderResponseWriter (w http.ResponseWriter) *StatusHeaderResponseWriter { - nw, ok := w.(WrappingResponseWriter) - if ok { - return &StatusHeaderResponseWriter{ - wrapped: nw.Wrapped(), - wroteHeader: false, - statusCode: 200, - headers: nw.GetHeaders(), - written: new(uint32), - } - } else { - return &StatusHeaderResponseWriter{ - wrapped: w, - wroteHeader: false, - statusCode: 200, - headers: nil, - written: new(uint32), - } + return &StatusHeaderResponseWriter{ + wrapped: w, + wroteHeader: false, + statusCode: 200, + headers: nil, + written: new(uint32), } } @@ -237,10 +226,6 @@ func (w *StatusHeaderResponseWriter) Wrapped() http.ResponseWriter { return w.wrapped } -func (w *StatusHeaderResponseWriter) GetHeaders() map[string][]*CustomHeader { - return w.headers -} - func (w *StatusHeaderResponseWriter) SetHeaders(h map[string][]*CustomHeader) { w.headers = h } diff --git a/vault/custom_response_headers_test.go b/vault/custom_response_headers_test.go index dabd4ef23fae..47e72a6bbce1 100644 --- a/vault/custom_response_headers_test.go +++ b/vault/custom_response_headers_test.go @@ -3,6 +3,7 @@ package vault import ( "context" "fmt" + "net/http" "net/http/httptest" "strings" "testing" @@ -127,8 +128,9 @@ func TestCustomResponseHeadersConfigInteractUiConfig(t *testing.T) { t.Fatalf("custom header config should be configured in core") } - w := httptest.NewRecorder() - hw := logical.NewStatusHeaderResponseWriter(w) + hw := func(w http.ResponseWriter) *http.ResponseWriter { + return &w + }(httptest.NewRecorder()) // setting a header that already exist in custom headers req := logical.TestRequest(t, logical.UpdateOperation, "config/ui/headers/X-Custom-Header") diff --git a/vault/logical_system.go b/vault/logical_system.go index bd602f21368c..3654fb00b43f 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -2972,7 +2972,7 @@ func (b *SystemBackend) handleMetrics(ctx context.Context, req *logical.Request, func (b *SystemBackend) handleMonitor(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { ll := data.Get("log_level").(string) - w := req.ResponseWriter + w := *req.ResponseWriter if ll == "" { ll = "info" @@ -2983,9 +2983,18 @@ func (b *SystemBackend) handleMonitor(ctx context.Context, req *logical.Request, return logical.ErrorResponse("unknown log level"), nil } - flusher, ok := w.Wrapped().(http.Flusher) + flusher, ok := w.(http.Flusher) if !ok { - return logical.ErrorResponse("streaming not supported"), nil + // http.ResponseWriter is wrapped in wrapGenericHandler, so let's + // access the underlying functionality + nw, ok := w.(logical.WrappingResponseWriter) + if !ok { + return logical.ErrorResponse("streaming not supported"), nil + } + flusher, ok = nw.Wrapped().(http.Flusher) + if !ok { + return logical.ErrorResponse("streaming not supported"), nil + } } isJson := b.Core.LogFormat() == "json" diff --git a/vault/logical_system_pprof.go b/vault/logical_system_pprof.go index ce7fc4b27a44..ce565eb2a868 100644 --- a/vault/logical_system_pprof.go +++ b/vault/logical_system_pprof.go @@ -159,7 +159,7 @@ func (b *SystemBackend) handlePprofIndex(ctx context.Context, req *logical.Reque return nil, err } - pprof.Index(req.ResponseWriter, req.HTTPRequest) + pprof.Index(*req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -168,7 +168,7 @@ func (b *SystemBackend) handlePprofCmdline(ctx context.Context, req *logical.Req return nil, err } - pprof.Cmdline(req.ResponseWriter, req.HTTPRequest) + pprof.Cmdline(*req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -177,7 +177,7 @@ func (b *SystemBackend) handlePprofGoroutine(ctx context.Context, req *logical.R return nil, err } - pprof.Handler("goroutine").ServeHTTP(req.ResponseWriter, req.HTTPRequest) + pprof.Handler("goroutine").ServeHTTP(*req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -186,7 +186,7 @@ func (b *SystemBackend) handlePprofHeap(ctx context.Context, req *logical.Reques return nil, err } - pprof.Handler("heap").ServeHTTP(req.ResponseWriter, req.HTTPRequest) + pprof.Handler("heap").ServeHTTP(*req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -195,7 +195,7 @@ func (b *SystemBackend) handlePprofAllocs(ctx context.Context, req *logical.Requ return nil, err } - pprof.Handler("allocs").ServeHTTP(req.ResponseWriter, req.HTTPRequest) + pprof.Handler("allocs").ServeHTTP(*req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -204,7 +204,7 @@ func (b *SystemBackend) handlePprofThreadcreate(ctx context.Context, req *logica return nil, err } - pprof.Handler("threadcreate").ServeHTTP(req.ResponseWriter, req.HTTPRequest) + pprof.Handler("threadcreate").ServeHTTP(*req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -213,7 +213,7 @@ func (b *SystemBackend) handlePprofBlock(ctx context.Context, req *logical.Reque return nil, err } - pprof.Handler("block").ServeHTTP(req.ResponseWriter, req.HTTPRequest) + pprof.Handler("block").ServeHTTP(*req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -222,7 +222,7 @@ func (b *SystemBackend) handlePprofMutex(ctx context.Context, req *logical.Reque return nil, err } - pprof.Handler("mutex").ServeHTTP(req.ResponseWriter, req.HTTPRequest) + pprof.Handler("mutex").ServeHTTP(*req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -243,7 +243,7 @@ func (b *SystemBackend) handlePprofProfile(ctx context.Context, req *logical.Req } } - pprof.Profile(req.ResponseWriter, req.HTTPRequest) + pprof.Profile(*req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -252,7 +252,7 @@ func (b *SystemBackend) handlePprofSymbol(ctx context.Context, req *logical.Requ return nil, err } - pprof.Symbol(req.ResponseWriter, req.HTTPRequest) + pprof.Symbol(*req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -273,7 +273,7 @@ func (b *SystemBackend) handlePprofTrace(ctx context.Context, req *logical.Reque } } - pprof.Trace(req.ResponseWriter, req.HTTPRequest) + pprof.Trace(*req.ResponseWriter, req.HTTPRequest) return nil, nil } diff --git a/vault/logical_system_raft.go b/vault/logical_system_raft.go index 667d9d648005..0dc62db0b9da 100644 --- a/vault/logical_system_raft.go +++ b/vault/logical_system_raft.go @@ -391,7 +391,7 @@ func (b *SystemBackend) handleStorageRaftSnapshotRead() framework.OperationFunc return nil, errors.New("no writer for request") } - err := raftStorage.SnapshotHTTP(req.ResponseWriter, b.Core.seal.GetAccess()) + err := raftStorage.SnapshotHTTP(*req.ResponseWriter, b.Core.seal.GetAccess()) if err != nil { return nil, err } From b26a4837430779306ea8dbd8e4fef4694657fa78 Mon Sep 17 00:00:00 2001 From: hamid ghaf Date: Tue, 23 Nov 2021 10:30:13 -0800 Subject: [PATCH 5/5] reverting change to HTTPResponseWriter --- http/handler.go | 12 +++----- http/logical.go | 9 ++---- physical/raft/raft.go | 4 +-- physical/raft/snapshot_test.go | 6 ++-- sdk/logical/request.go | 2 +- sdk/logical/response.go | 44 ++++++++++++++++++--------- vault/custom_response_headers_test.go | 6 ++-- vault/logical_system.go | 6 ++-- vault/logical_system_pprof.go | 22 +++++++------- vault/logical_system_raft.go | 2 +- 10 files changed, 61 insertions(+), 52 deletions(-) diff --git a/http/handler.go b/http/handler.go index 75b358f9ffcd..01085884b74d 100644 --- a/http/handler.go +++ b/http/handler.go @@ -305,17 +305,18 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - nw := logical.NewStatusHeaderResponseWriter(w) // This block needs to be here so that upon sending SIGHUP, custom response // headers are also reloaded into the handlers. + var customHeaders map[string][]*logical.CustomHeader if props.ListenerConfig != nil { la := props.ListenerConfig.Address listenerCustomHeaders := core.GetListenerCustomResponseHeaders(la) if listenerCustomHeaders != nil { - nw.SetHeaders(listenerCustomHeaders.StatusCodeHeaderMap) + customHeaders = listenerCustomHeaders.StatusCodeHeaderMap } } + nw := logical.NewStatusHeaderResponseWriter(w, customHeaders) // Set the Cache-Control header for all the responses returned // by Vault nw.Header().Set("Cache-Control", "no-store") @@ -906,11 +907,8 @@ func request(core *vault.Core, w http.ResponseWriter, rawReq *http.Request, r *l // additional output. Headers have already been sent. If the response writer // is set but has not been written to it likely means there was some kind of // error - if r.ResponseWriter != nil { - w, ok := (*r.ResponseWriter).(logical.WrappingResponseWriter) - if ok && w.Written() { - return nil, true, false - } + if r.ResponseWriter != nil && r.ResponseWriter.Written() { + return nil, true, false } if respondErrorCommon(w, r, resp, err) { diff --git a/http/logical.go b/http/logical.go index eee51a298048..a7730033b351 100644 --- a/http/logical.go +++ b/http/logical.go @@ -199,7 +199,7 @@ func buildLogicalRequestNoAuth(perfStandby bool, w http.ResponseWriter, r *http. req.HTTPRequest = r } if responseWriter != nil { - req.ResponseWriter = &responseWriter + req.ResponseWriter = logical.NewHTTPResponseWriter(responseWriter) } return req, origBody, 0, nil @@ -368,11 +368,8 @@ func respondLogical(core *vault.Core, w http.ResponseWriter, r *http.Request, re // If vault's core has already written to the response writer do not add any // additional output. Headers have already been sent. - if req != nil && req.ResponseWriter != nil { - w, ok := (*req.ResponseWriter).(logical.WrappingResponseWriter) - if ok && w.Written() { - return - } + if req != nil && req.ResponseWriter != nil && req.ResponseWriter.Written() { + return } if resp != nil { diff --git a/physical/raft/raft.go b/physical/raft/raft.go index 699a47c356fe..47b96d7c0dcb 100644 --- a/physical/raft/raft.go +++ b/physical/raft/raft.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "io/ioutil" - "net/http" "os" "path/filepath" "strconv" @@ -28,6 +27,7 @@ import ( "github.com/hashicorp/vault/helper/metricsutil" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/jsonutil" + "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/physical" "github.com/hashicorp/vault/vault/cluster" "github.com/hashicorp/vault/vault/seal" @@ -1138,7 +1138,7 @@ func (b *RaftBackend) Peers(ctx context.Context) ([]Peer, error) { // SnapshotHTTP is a wrapper for Snapshot that sends the snapshot as an HTTP // response. -func (b *RaftBackend) SnapshotHTTP(out http.ResponseWriter, access *seal.Access) error { +func (b *RaftBackend) SnapshotHTTP(out *logical.HTTPResponseWriter, access *seal.Access) error { out.Header().Add("Content-Disposition", "attachment") out.Header().Add("Content-Type", "application/gzip") diff --git a/physical/raft/snapshot_test.go b/physical/raft/snapshot_test.go index 9d547bfadae9..53757c5683d7 100644 --- a/physical/raft/snapshot_test.go +++ b/physical/raft/snapshot_test.go @@ -17,6 +17,7 @@ import ( hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/raft" + "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/physical" "github.com/hashicorp/vault/sdk/plugin/pb" ) @@ -472,7 +473,8 @@ func TestRaft_Snapshot_Take_Restore(t *testing.T) { } } - snap := httptest.NewRecorder() + recorder := httptest.NewRecorder() + snap := logical.NewHTTPResponseWriter(recorder) err := raft1.Snapshot(snap, nil) if err != nil { @@ -490,7 +492,7 @@ func TestRaft_Snapshot_Take_Restore(t *testing.T) { } } - snapFile, cleanup, metadata, err := raft1.WriteSnapshotToTemp(ioutil.NopCloser(snap.Body), nil) + snapFile, cleanup, metadata, err := raft1.WriteSnapshotToTemp(ioutil.NopCloser(recorder.Body), nil) if err != nil { t.Fatal(err) } diff --git a/sdk/logical/request.go b/sdk/logical/request.go index 08cedc447163..c44b8dd5a82c 100644 --- a/sdk/logical/request.go +++ b/sdk/logical/request.go @@ -203,7 +203,7 @@ type Request struct { // ResponseWriter if set can be used to stream a response value to the http // request that generated this logical.Request object. - ResponseWriter *http.ResponseWriter `json:"-" sentinel:""` + ResponseWriter *HTTPResponseWriter `json:"-" sentinel:""` // requiredState is used internally to propagate the X-Vault-Index request // header to later levels of request processing that operate only on diff --git a/sdk/logical/response.go b/sdk/logical/response.go index 353dc50f72b0..19a080c7699d 100644 --- a/sdk/logical/response.go +++ b/sdk/logical/response.go @@ -193,10 +193,36 @@ func RespondWithStatusCode(resp *Response, req *Request, code int) (*Response, e return ret, nil } +// HTTPResponseWriter is optionally added to a request object and can be used to +// write directly to the HTTP response writer. +type HTTPResponseWriter struct { + http.ResponseWriter + written *uint32 +} + +// NewHTTPResponseWriter creates a new HTTPResponseWriter object that wraps the +// provided io.Writer. +func NewHTTPResponseWriter(w http.ResponseWriter) *HTTPResponseWriter { + return &HTTPResponseWriter{ + ResponseWriter: w, + written: new(uint32), + } +} + +// Write will write the bytes to the underlying io.Writer. +func (w *HTTPResponseWriter) Write(bytes []byte) (int, error) { + atomic.StoreUint32(w.written, 1) + return w.ResponseWriter.Write(bytes) +} + +// Written tells us if the writer has been written to yet. +func (w *HTTPResponseWriter) Written() bool { + return atomic.LoadUint32(w.written) == 1 +} + type WrappingResponseWriter interface { http.ResponseWriter Wrapped() http.ResponseWriter - Written() bool } type StatusHeaderResponseWriter struct { @@ -204,32 +230,21 @@ type StatusHeaderResponseWriter struct { wroteHeader bool statusCode int headers map[string][]*CustomHeader - written *uint32 } -func NewStatusHeaderResponseWriter (w http.ResponseWriter) *StatusHeaderResponseWriter { +func NewStatusHeaderResponseWriter(w http.ResponseWriter, h map[string][]*CustomHeader) *StatusHeaderResponseWriter { return &StatusHeaderResponseWriter{ wrapped: w, wroteHeader: false, statusCode: 200, - headers: nil, - written: new(uint32), + headers: h, } } -// Written tells us if the writer has been written to yet. -func (w *StatusHeaderResponseWriter) Written() bool { - return atomic.LoadUint32(w.written) == 1 -} - func (w *StatusHeaderResponseWriter) Wrapped() http.ResponseWriter { return w.wrapped } -func (w *StatusHeaderResponseWriter) SetHeaders(h map[string][]*CustomHeader) { - w.headers = h -} - func (w *StatusHeaderResponseWriter) Header() http.Header { return w.wrapped.Header() } @@ -246,7 +261,6 @@ func (w *StatusHeaderResponseWriter) Write(buf []byte) (int, error) { if !w.wroteHeader { w.setCustomResponseHeaders(w.statusCode) } - atomic.StoreUint32(w.written, 1) return w.wrapped.Write(buf) } diff --git a/vault/custom_response_headers_test.go b/vault/custom_response_headers_test.go index 47e72a6bbce1..4b370a943f42 100644 --- a/vault/custom_response_headers_test.go +++ b/vault/custom_response_headers_test.go @@ -3,7 +3,6 @@ package vault import ( "context" "fmt" - "net/http" "net/http/httptest" "strings" "testing" @@ -128,9 +127,8 @@ func TestCustomResponseHeadersConfigInteractUiConfig(t *testing.T) { t.Fatalf("custom header config should be configured in core") } - hw := func(w http.ResponseWriter) *http.ResponseWriter { - return &w - }(httptest.NewRecorder()) + w := httptest.NewRecorder() + hw := logical.NewHTTPResponseWriter(w) // setting a header that already exist in custom headers req := logical.TestRequest(t, logical.UpdateOperation, "config/ui/headers/X-Custom-Header") diff --git a/vault/logical_system.go b/vault/logical_system.go index 3654fb00b43f..f2f8d6ae76da 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -2972,7 +2972,7 @@ func (b *SystemBackend) handleMetrics(ctx context.Context, req *logical.Request, func (b *SystemBackend) handleMonitor(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { ll := data.Get("log_level").(string) - w := *req.ResponseWriter + w := req.ResponseWriter if ll == "" { ll = "info" @@ -2983,11 +2983,11 @@ func (b *SystemBackend) handleMonitor(ctx context.Context, req *logical.Request, return logical.ErrorResponse("unknown log level"), nil } - flusher, ok := w.(http.Flusher) + flusher, ok := w.ResponseWriter.(http.Flusher) if !ok { // http.ResponseWriter is wrapped in wrapGenericHandler, so let's // access the underlying functionality - nw, ok := w.(logical.WrappingResponseWriter) + nw, ok := w.ResponseWriter.(logical.WrappingResponseWriter) if !ok { return logical.ErrorResponse("streaming not supported"), nil } diff --git a/vault/logical_system_pprof.go b/vault/logical_system_pprof.go index ce565eb2a868..ce7fc4b27a44 100644 --- a/vault/logical_system_pprof.go +++ b/vault/logical_system_pprof.go @@ -159,7 +159,7 @@ func (b *SystemBackend) handlePprofIndex(ctx context.Context, req *logical.Reque return nil, err } - pprof.Index(*req.ResponseWriter, req.HTTPRequest) + pprof.Index(req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -168,7 +168,7 @@ func (b *SystemBackend) handlePprofCmdline(ctx context.Context, req *logical.Req return nil, err } - pprof.Cmdline(*req.ResponseWriter, req.HTTPRequest) + pprof.Cmdline(req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -177,7 +177,7 @@ func (b *SystemBackend) handlePprofGoroutine(ctx context.Context, req *logical.R return nil, err } - pprof.Handler("goroutine").ServeHTTP(*req.ResponseWriter, req.HTTPRequest) + pprof.Handler("goroutine").ServeHTTP(req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -186,7 +186,7 @@ func (b *SystemBackend) handlePprofHeap(ctx context.Context, req *logical.Reques return nil, err } - pprof.Handler("heap").ServeHTTP(*req.ResponseWriter, req.HTTPRequest) + pprof.Handler("heap").ServeHTTP(req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -195,7 +195,7 @@ func (b *SystemBackend) handlePprofAllocs(ctx context.Context, req *logical.Requ return nil, err } - pprof.Handler("allocs").ServeHTTP(*req.ResponseWriter, req.HTTPRequest) + pprof.Handler("allocs").ServeHTTP(req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -204,7 +204,7 @@ func (b *SystemBackend) handlePprofThreadcreate(ctx context.Context, req *logica return nil, err } - pprof.Handler("threadcreate").ServeHTTP(*req.ResponseWriter, req.HTTPRequest) + pprof.Handler("threadcreate").ServeHTTP(req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -213,7 +213,7 @@ func (b *SystemBackend) handlePprofBlock(ctx context.Context, req *logical.Reque return nil, err } - pprof.Handler("block").ServeHTTP(*req.ResponseWriter, req.HTTPRequest) + pprof.Handler("block").ServeHTTP(req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -222,7 +222,7 @@ func (b *SystemBackend) handlePprofMutex(ctx context.Context, req *logical.Reque return nil, err } - pprof.Handler("mutex").ServeHTTP(*req.ResponseWriter, req.HTTPRequest) + pprof.Handler("mutex").ServeHTTP(req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -243,7 +243,7 @@ func (b *SystemBackend) handlePprofProfile(ctx context.Context, req *logical.Req } } - pprof.Profile(*req.ResponseWriter, req.HTTPRequest) + pprof.Profile(req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -252,7 +252,7 @@ func (b *SystemBackend) handlePprofSymbol(ctx context.Context, req *logical.Requ return nil, err } - pprof.Symbol(*req.ResponseWriter, req.HTTPRequest) + pprof.Symbol(req.ResponseWriter, req.HTTPRequest) return nil, nil } @@ -273,7 +273,7 @@ func (b *SystemBackend) handlePprofTrace(ctx context.Context, req *logical.Reque } } - pprof.Trace(*req.ResponseWriter, req.HTTPRequest) + pprof.Trace(req.ResponseWriter, req.HTTPRequest) return nil, nil } diff --git a/vault/logical_system_raft.go b/vault/logical_system_raft.go index 0dc62db0b9da..667d9d648005 100644 --- a/vault/logical_system_raft.go +++ b/vault/logical_system_raft.go @@ -391,7 +391,7 @@ func (b *SystemBackend) handleStorageRaftSnapshotRead() framework.OperationFunc return nil, errors.New("no writer for request") } - err := raftStorage.SnapshotHTTP(*req.ResponseWriter, b.Core.seal.GetAccess()) + err := raftStorage.SnapshotHTTP(req.ResponseWriter, b.Core.seal.GetAccess()) if err != nil { return nil, err }