vault/http/logical.go
Jeff Mitchell fd2c0f033e Add the ability for warnings to be added to responses. These are
marshalled into JSON or displayed from the CLI depending on the output
mode. This allows conferring information such as "no such policy exists"
when creating a token -- not an error, but something the user should be
aware of.

Fixes #676
2015-10-07 16:18:39 -04:00

213 lines
5.2 KiB
Go

package http
import (
"io"
"net"
"net/http"
"strings"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/vault"
)
func handleLogical(core *vault.Core, dataOnly bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Determine the path...
if !strings.HasPrefix(r.URL.Path, "/v1/") {
respondError(w, http.StatusNotFound, nil)
return
}
path := r.URL.Path[len("/v1/"):]
if path == "" {
respondError(w, http.StatusNotFound, nil)
return
}
// Determine the operation
var op logical.Operation
switch r.Method {
case "DELETE":
op = logical.DeleteOperation
case "GET":
op = logical.ReadOperation
case "POST":
fallthrough
case "PUT":
op = logical.WriteOperation
default:
respondError(w, http.StatusMethodNotAllowed, nil)
return
}
// Parse the request if we can
var req map[string]interface{}
if op == logical.WriteOperation {
err := parseRequest(r, &req)
if err == io.EOF {
req = nil
err = nil
}
if err != nil {
respondError(w, http.StatusBadRequest, err)
return
}
}
// Make the internal request. We attach the connection info
// as well in case this is an authentication request that requires
// it. Vault core handles stripping this if we need to.
resp, ok := request(core, w, r, requestAuth(r, &logical.Request{
Operation: op,
Path: path,
Data: req,
Connection: getConnection(r),
}))
if !ok {
return
}
if op == logical.ReadOperation && resp == nil {
respondError(w, http.StatusNotFound, nil)
return
}
// Build the proper response
respondLogical(w, r, path, dataOnly, resp)
})
}
func respondLogical(w http.ResponseWriter, r *http.Request, path string, dataOnly bool, resp *logical.Response) {
var httpResp interface{}
if resp != nil {
if resp.Redirect != "" {
// If we have a redirect, redirect! We use a 307 code
// because we don't actually know if its permanent.
http.Redirect(w, r, resp.Redirect, 307)
return
}
if dataOnly {
respondOk(w, resp.Data)
return
}
// Check if this is a raw response
if _, ok := resp.Data[logical.HTTPContentType]; ok {
respondRaw(w, r, path, resp)
return
}
logicalResp := &LogicalResponse{
Data: resp.Data,
Warnings: resp.GetWarnings(),
}
if resp.Secret != nil {
logicalResp.LeaseID = resp.Secret.LeaseID
logicalResp.Renewable = resp.Secret.Renewable
logicalResp.LeaseDuration = int(resp.Secret.TTL.Seconds())
}
// If we have authentication information, then
// set up the result structure.
if resp.Auth != nil {
logicalResp.Auth = &Auth{
ClientToken: resp.Auth.ClientToken,
Policies: resp.Auth.Policies,
Metadata: resp.Auth.Metadata,
LeaseDuration: int(resp.Auth.TTL.Seconds()),
Renewable: resp.Auth.Renewable,
}
}
httpResp = logicalResp
}
// Respond
respondOk(w, httpResp)
}
// respondRaw is used when the response is using HTTPContentType and HTTPRawBody
// to change the default response handling. This is only used for specific things like
// returning the CRL information on the PKI backends.
func respondRaw(w http.ResponseWriter, r *http.Request, path string, resp *logical.Response) {
// Ensure this is never a secret or auth response
if resp.Secret != nil || resp.Auth != nil {
respondError(w, http.StatusInternalServerError, nil)
return
}
// Get the status code
statusRaw, ok := resp.Data[logical.HTTPStatusCode]
if !ok {
respondError(w, http.StatusInternalServerError, nil)
return
}
status, ok := statusRaw.(int)
if !ok {
respondError(w, http.StatusInternalServerError, nil)
return
}
// Get the header
contentTypeRaw, ok := resp.Data[logical.HTTPContentType]
if !ok {
respondError(w, http.StatusInternalServerError, nil)
return
}
contentType, ok := contentTypeRaw.(string)
if !ok {
respondError(w, http.StatusInternalServerError, nil)
return
}
// Get the body
bodyRaw, ok := resp.Data[logical.HTTPRawBody]
if !ok {
respondError(w, http.StatusInternalServerError, nil)
return
}
body, ok := bodyRaw.([]byte)
if !ok {
respondError(w, http.StatusInternalServerError, nil)
return
}
// Write the response
w.Header().Set("Content-Type", contentType)
w.WriteHeader(status)
w.Write(body)
}
// getConnection is used to format the connection information for
// attaching to a logical request
func getConnection(r *http.Request) (connection *logical.Connection) {
var remoteAddr string
remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
remoteAddr = ""
}
connection = &logical.Connection{
RemoteAddr: remoteAddr,
ConnState: r.TLS,
}
return
}
type LogicalResponse struct {
LeaseID string `json:"lease_id"`
Renewable bool `json:"renewable"`
LeaseDuration int `json:"lease_duration"`
Data map[string]interface{} `json:"data"`
Warnings []string `json:"warnings"`
Auth *Auth `json:"auth"`
}
type Auth struct {
ClientToken string `json:"client_token"`
Policies []string `json:"policies"`
Metadata map[string]string `json:"metadata"`
LeaseDuration int `json:"lease_duration"`
Renewable bool `json:"renewable"`
}