open-vault/command/agent/cache/handler.go
Vishal Nayak feb235d5f8
Vault Agent Cache (#6220)
* vault-agent-cache: squashed 250+ commits

* Add proper token revocation validations to the tests

* Add more test cases

* Avoid leaking by not closing request/response bodies; add comments

* Fix revoke orphan use case; update tests

* Add CLI test for making request over unix socket

* agent/cache: remove namespace-related tests

* Strip-off the auto-auth token from the lookup response

* Output listener details along with configuration

* Add scheme to API address output

* leasecache: use IndexNameLease for prefix lease revocations

* Make CLI accept the fully qualified unix address

* export VAULT_AGENT_ADDR=unix://path/to/socket

* unix:/ to unix://
2019-02-14 20:10:36 -05:00

156 lines
4.4 KiB
Go

package cache
import (
"bufio"
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"github.com/hashicorp/errwrap"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/helper/consts"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/logical"
)
func Handler(ctx context.Context, logger hclog.Logger, proxier Proxier, useAutoAuthToken bool, client *api.Client) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger.Info("received request", "path", r.URL.Path, "method", r.Method)
token := r.Header.Get(consts.AuthHeaderName)
if token == "" && useAutoAuthToken {
logger.Debug("using auto auth token")
token = client.Token()
}
// Parse and reset body.
reqBody, err := ioutil.ReadAll(r.Body)
if err != nil {
logger.Error("failed to read request body")
respondError(w, http.StatusInternalServerError, errors.New("failed to read request body"))
}
if r.Body != nil {
r.Body.Close()
}
r.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody))
req := &SendRequest{
Token: token,
Request: r,
RequestBody: reqBody,
}
resp, err := proxier.Send(ctx, req)
if err != nil {
respondError(w, http.StatusInternalServerError, errwrap.Wrapf("failed to get the response: {{err}}", err))
return
}
err = processTokenLookupResponse(ctx, logger, useAutoAuthToken, client, req, resp)
if err != nil {
respondError(w, http.StatusInternalServerError, errwrap.Wrapf("failed to process token lookup response: {{err}}", err))
return
}
defer resp.Response.Body.Close()
copyHeader(w.Header(), resp.Response.Header)
w.WriteHeader(resp.Response.StatusCode)
io.Copy(w, resp.Response.Body)
return
})
}
// processTokenLookupResponse checks if the request was one of token
// lookup-self. If the auto-auth token was used to perform lookup-self, the
// identifier of the token and its accessor same will be stripped off of the
// response.
func processTokenLookupResponse(ctx context.Context, logger hclog.Logger, useAutoAuthToken bool, client *api.Client, req *SendRequest, resp *SendResponse) error {
// If auto-auth token is not being used, there is nothing to do.
if !useAutoAuthToken {
return nil
}
// If lookup responded with non 200 status, there is nothing to do.
if resp.Response.StatusCode != http.StatusOK {
return nil
}
// Strip-off namespace related information from the request and get the
// relative path of the request.
_, path := deriveNamespaceAndRevocationPath(req)
if path == vaultPathTokenLookupSelf {
logger.Info("stripping auto-auth token from the response", "path", req.Request.URL.Path, "method", req.Request.Method)
secret, err := api.ParseSecret(bytes.NewBuffer(resp.ResponseBody))
if err != nil {
return fmt.Errorf("failed to parse token lookup response: %v", err)
}
if secret != nil && secret.Data != nil && secret.Data["id"] != nil {
token, ok := secret.Data["id"].(string)
if !ok {
return fmt.Errorf("failed to type assert the token id in the response")
}
if token == client.Token() {
delete(secret.Data, "id")
delete(secret.Data, "accessor")
}
bodyBytes, err := json.Marshal(secret)
if err != nil {
return err
}
if resp.Response.Body != nil {
resp.Response.Body.Close()
}
resp.Response.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
resp.Response.ContentLength = int64(len(bodyBytes))
// Serialize and re-read the reponse
var respBytes bytes.Buffer
err = resp.Response.Write(&respBytes)
if err != nil {
return fmt.Errorf("failed to serialize the updated response: %v", err)
}
updatedResponse, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(respBytes.Bytes())), nil)
if err != nil {
return fmt.Errorf("failed to deserialize the updated response: %v", err)
}
resp.Response = &api.Response{
Response: updatedResponse,
}
resp.ResponseBody = bodyBytes
}
}
return nil
}
func copyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
func respondError(w http.ResponseWriter, status int, err error) {
logical.AdjustErrorStatusCode(&status, err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
resp := &vaulthttp.ErrorResponse{Errors: make([]string, 0, 1)}
if err != nil {
resp.Errors = append(resp.Errors, err.Error())
}
enc := json.NewEncoder(w)
enc.Encode(resp)
}