open-vault/http/logical.go

216 lines
5.5 KiB
Go
Raw Normal View History

package http
import (
2015-04-07 21:36:17 +00:00
"io"
"net"
"net/http"
"strings"
"time"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/vault"
)
func handleLogical(core *vault.Core) 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 {
2015-04-07 18:04:06 +00:00
case "DELETE":
op = logical.DeleteOperation
case "GET":
op = logical.ReadOperation
2015-04-07 21:00:09 +00:00
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 {
2015-04-07 21:36:17 +00:00
err := parseRequest(r, &req)
if err == io.EOF {
req = nil
err = nil
}
if err != nil {
respondError(w, http.StatusBadRequest, err)
return
}
}
// http.Server will set RemoteAddr to an "IP:port" string
var remoteAddr string
remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
remoteAddr = ""
}
// 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.
2015-04-19 21:36:50 +00:00
resp, ok := request(core, w, r, requestAuth(r, &logical.Request{
Operation: op,
Path: path,
Data: req,
Connection: &logical.Connection{
RemoteAddr: remoteAddr,
ConnState: r.TLS,
},
2015-03-29 23:14:54 +00:00
}))
2015-04-08 18:19:03 +00:00
if !ok {
return
}
if op == logical.ReadOperation && resp == nil {
respondError(w, http.StatusNotFound, nil)
return
}
2015-04-14 00:21:31 +00:00
// Build the proper response
respondLogical(w, r, path, resp)
2015-04-14 00:21:31 +00:00
})
}
func respondLogical(w http.ResponseWriter, r *http.Request, path string, resp *logical.Response) {
2015-04-14 00:21:31 +00:00
var httpResp interface{}
if resp != nil {
if resp.Redirect != "" {
// If we have a redirect, redirect! We use a 302 code
// because we don't actually know if its permanent.
http.Redirect(w, r, resp.Redirect, 302)
return
}
2015-05-27 21:10:00 +00:00
// Check if this is a raw response
if _, ok := resp.Data[logical.HTTPContentType]; ok {
respondRaw(w, r, path, resp)
return
}
2015-04-14 00:21:31 +00:00
logicalResp := &LogicalResponse{Data: resp.Data}
if resp.Secret != nil {
logicalResp.LeaseID = resp.Secret.LeaseID
logicalResp.Renewable = resp.Secret.Renewable
logicalResp.LeaseDuration = int(resp.Secret.Lease.Seconds())
}
// If we have authentication information, then set the cookie
// and setup the result structure.
if resp.Auth != nil {
expireDuration := 365 * 24 * time.Hour
if logicalResp.LeaseDuration != 0 {
expireDuration =
time.Duration(logicalResp.LeaseDuration) * time.Second
}
// Do not set the token as the auth cookie if the endpoint
// is the token store. Otherwise, attempting to create a token
// will cause the client to be authenticated as that token.
if !strings.HasPrefix(path, "auth/token/") {
http.SetCookie(w, &http.Cookie{
Name: AuthCookieName,
Value: resp.Auth.ClientToken,
Path: "/",
Expires: time.Now().UTC().Add(expireDuration),
})
}
2015-04-14 00:21:31 +00:00
logicalResp.Auth = &Auth{
ClientToken: resp.Auth.ClientToken,
Policies: resp.Auth.Policies,
Metadata: resp.Auth.Metadata,
LeaseDuration: int(resp.Auth.Lease.Seconds()),
Renewable: resp.Auth.Renewable,
}
}
2015-04-14 00:21:31 +00:00
httpResp = logicalResp
}
// Respond
respondOk(w, httpResp)
}
2015-05-27 21:10:00 +00:00
// 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)
}
type LogicalResponse struct {
LeaseID string `json:"lease_id"`
2015-03-16 20:29:51 +00:00
Renewable bool `json:"renewable"`
LeaseDuration int `json:"lease_duration"`
Data map[string]interface{} `json:"data"`
2015-04-04 22:40:41 +00:00
Auth *Auth `json:"auth"`
}
type Auth struct {
ClientToken string `json:"client_token"`
2015-04-04 22:40:41 +00:00
Policies []string `json:"policies"`
Metadata map[string]string `json:"metadata"`
LeaseDuration int `json:"lease_duration"`
Renewable bool `json:"renewable"`
}