// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package logical import ( "bufio" "encoding/json" "errors" "fmt" "net" "net/http" "strconv" "sync/atomic" "github.com/hashicorp/vault/sdk/helper/wrapping" ) const ( // HTTPContentType can be specified in the Data field of a Response // so that the HTTP front end can specify a custom Content-Type associated // with the HTTPRawBody. This can only be used for non-secrets, and should // be avoided unless absolutely necessary, such as implementing a specification. // The value must be a string. HTTPContentType = "http_content_type" // HTTPRawBody is the raw content of the HTTP body that goes with the HTTPContentType. // This can only be specified for non-secrets, and should should be similarly // avoided like the HTTPContentType. The value must be a byte slice. HTTPRawBody = "http_raw_body" // HTTPStatusCode is the response code of the HTTP body that goes with the HTTPContentType. // This can only be specified for non-secrets, and should should be similarly // avoided like the HTTPContentType. The value must be an integer. HTTPStatusCode = "http_status_code" // For unwrapping we may need to know whether the value contained in the // raw body is already JSON-unmarshaled. The presence of this key indicates // that it has already been unmarshaled. That way we don't need to simply // ignore errors. HTTPRawBodyAlreadyJSONDecoded = "http_raw_body_already_json_decoded" // If set, HTTPCacheControlHeader will replace the default Cache-Control=no-store header // set by the generic wrapping handler. The value must be a string. HTTPCacheControlHeader = "http_raw_cache_control" // If set, HTTPPragmaHeader will set the Pragma response header. // The value must be a string. HTTPPragmaHeader = "http_raw_pragma" // If set, HTTPWWWAuthenticateHeader will set the WWW-Authenticate response header. // The value must be a string. HTTPWWWAuthenticateHeader = "http_www_authenticate" ) // Response is a struct that stores the response of a request. // It is used to abstract the details of the higher level request protocol. type Response struct { // Secret, if not nil, denotes that this response represents a secret. Secret *Secret `json:"secret" structs:"secret" mapstructure:"secret"` // Auth, if not nil, contains the authentication information for // this response. This is only checked and means something for // credential backends. Auth *Auth `json:"auth" structs:"auth" mapstructure:"auth"` // Response data is an opaque map that must have string keys. For // secrets, this data is sent down to the user as-is. To store internal // data that you don't want the user to see, store it in // Secret.InternalData. Data map[string]interface{} `json:"data" structs:"data" mapstructure:"data"` // Redirect is an HTTP URL to redirect to for further authentication. // This is only valid for credential backends. This will be blanked // for any logical backend and ignored. Redirect string `json:"redirect" structs:"redirect" mapstructure:"redirect"` // Warnings allow operations or backends to return warnings in response // to user actions without failing the action outright. Warnings []string `json:"warnings" structs:"warnings" mapstructure:"warnings"` // Information for wrapping the response in a cubbyhole WrapInfo *wrapping.ResponseWrapInfo `json:"wrap_info" structs:"wrap_info" mapstructure:"wrap_info"` // Headers will contain the http headers from the plugin that it wishes to // have as part of the output Headers map[string][]string `json:"headers" structs:"headers" mapstructure:"headers"` } // AddWarning adds a warning into the response's warning list func (r *Response) AddWarning(warning string) { if r.Warnings == nil { r.Warnings = make([]string, 0, 1) } r.Warnings = append(r.Warnings, warning) } // IsError returns true if this response seems to indicate an error. func (r *Response) IsError() bool { // If the response data contains only an 'error' element, or an 'error' and a 'data' element only return r != nil && r.Data != nil && r.Data["error"] != nil && (len(r.Data) == 1 || (r.Data["data"] != nil && len(r.Data) == 2)) } func (r *Response) Error() error { if !r.IsError() { return nil } switch r.Data["error"].(type) { case string: return errors.New(r.Data["error"].(string)) case error: return r.Data["error"].(error) } return nil } // HelpResponse is used to format a help response func HelpResponse(text string, seeAlso []string, oapiDoc interface{}) *Response { return &Response{ Data: map[string]interface{}{ "help": text, "see_also": seeAlso, "openapi": oapiDoc, }, } } // ErrorResponse is used to format an error response func ErrorResponse(text string, vargs ...interface{}) *Response { if len(vargs) > 0 { text = fmt.Sprintf(text, vargs...) } return &Response{ Data: map[string]interface{}{ "error": text, }, } } // ListResponse is used to format a response to a list operation. func ListResponse(keys []string) *Response { resp := &Response{ Data: map[string]interface{}{}, } if len(keys) != 0 { resp.Data["keys"] = keys } return resp } // ListResponseWithInfo is used to format a response to a list operation and // return the keys as well as a map with corresponding key info. func ListResponseWithInfo(keys []string, keyInfo map[string]interface{}) *Response { resp := ListResponse(keys) keyInfoData := make(map[string]interface{}) for _, key := range keys { val, ok := keyInfo[key] if ok { keyInfoData[key] = val } } if len(keyInfoData) > 0 { resp.Data["key_info"] = keyInfoData } return resp } // RespondWithStatusCode takes a response and converts it to a raw response with // the provided Status Code. func RespondWithStatusCode(resp *Response, req *Request, code int) (*Response, error) { ret := &Response{ Data: map[string]interface{}{ HTTPContentType: "application/json", HTTPStatusCode: code, }, } if resp != nil { httpResp := LogicalResponseToHTTPResponse(resp) if req != nil { httpResp.RequestID = req.ID } body, err := json.Marshal(httpResp) if err != nil { return nil, err } // We default to string here so that the value is HMAC'd via audit. // Since this function is always marshaling to JSON, this is // appropriate. ret.Data[HTTPRawBody] = string(body) } 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 } type StatusHeaderResponseWriter struct { wrapped http.ResponseWriter wroteHeader bool StatusCode int headers map[string][]*CustomHeader } func NewStatusHeaderResponseWriter(w http.ResponseWriter, h map[string][]*CustomHeader) *StatusHeaderResponseWriter { return &StatusHeaderResponseWriter{ wrapped: w, wroteHeader: false, StatusCode: 200, headers: h, } } func (w *StatusHeaderResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { if h, ok := w.wrapped.(http.Hijacker); ok { return h.Hijack() } return nil, nil, fmt.Errorf("could not hijack because wrapped connection is %T and it does not implement http.Hijacker", w.wrapped) } 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 { 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{} // ResolveRoleResponse returns a standard response to be returned by functions handling a ResolveRoleOperation func ResolveRoleResponse(roleName string) (*Response, error) { return &Response{ Data: map[string]interface{}{ "role": roleName, }, }, nil }