mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	 672cdc0fdb
			
		
	
	672cdc0fdb
	
	
	
		
			
			* VAULT-11510 Vault Agent can start listeners without caching * VAULT-11510 fix order of imports * VAULT-11510 changelog * VAULT-11510 typo and better switch * VAULT-11510 update name * VAULT-11510 New api_proxy stanza to configure API proxy * VAULT-11510 First pass at API Proxy docs * VAULT-11510 nav data * VAULT-11510 typo * VAULT-11510 docs update
		
			
				
	
	
		
			230 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			230 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package cache
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"io/ioutil"
 | |
| 	"net/http"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/armon/go-metrics"
 | |
| 	"github.com/hashicorp/go-hclog"
 | |
| 	"github.com/hashicorp/vault/api"
 | |
| 	"github.com/hashicorp/vault/command/agent/sink"
 | |
| 	"github.com/hashicorp/vault/sdk/helper/consts"
 | |
| 	"github.com/hashicorp/vault/sdk/logical"
 | |
| )
 | |
| 
 | |
| func ProxyHandler(ctx context.Context, logger hclog.Logger, proxier Proxier, inmemSink sink.Sink, proxyVaultToken bool) http.Handler {
 | |
| 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | |
| 		logger.Info("received request", "method", r.Method, "path", r.URL.Path)
 | |
| 
 | |
| 		if !proxyVaultToken {
 | |
| 			r.Header.Del(consts.AuthHeaderName)
 | |
| 		}
 | |
| 
 | |
| 		token := r.Header.Get(consts.AuthHeaderName)
 | |
| 
 | |
| 		if token == "" && inmemSink != nil {
 | |
| 			logger.Debug("using auto auth token", "method", r.Method, "path", r.URL.Path)
 | |
| 			token = inmemSink.(sink.SinkReader).Token()
 | |
| 		}
 | |
| 
 | |
| 		// Parse and reset body.
 | |
| 		reqBody, err := io.ReadAll(r.Body)
 | |
| 		if err != nil {
 | |
| 			logger.Error("failed to read request body")
 | |
| 			logical.RespondError(w, http.StatusInternalServerError, errors.New("failed to read request body"))
 | |
| 			return
 | |
| 		}
 | |
| 		if r.Body != nil {
 | |
| 			r.Body.Close()
 | |
| 		}
 | |
| 		r.Body = io.NopCloser(bytes.NewReader(reqBody))
 | |
| 		req := &SendRequest{
 | |
| 			Token:       token,
 | |
| 			Request:     r,
 | |
| 			RequestBody: reqBody,
 | |
| 		}
 | |
| 
 | |
| 		resp, err := proxier.Send(ctx, req)
 | |
| 		if err != nil {
 | |
| 			// If this is an api.Response error, don't wrap the response.
 | |
| 			if resp != nil && resp.Response.Error() != nil {
 | |
| 				copyHeader(w.Header(), resp.Response.Header)
 | |
| 				w.WriteHeader(resp.Response.StatusCode)
 | |
| 				io.Copy(w, resp.Response.Body)
 | |
| 				metrics.IncrCounter([]string{"agent", "proxy", "client_error"}, 1)
 | |
| 			} else {
 | |
| 				metrics.IncrCounter([]string{"agent", "proxy", "error"}, 1)
 | |
| 				logical.RespondError(w, http.StatusInternalServerError, fmt.Errorf("failed to get the response: %w", err))
 | |
| 			}
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		err = sanitizeAutoAuthTokenResponse(ctx, logger, inmemSink, req, resp)
 | |
| 		if err != nil {
 | |
| 			logical.RespondError(w, http.StatusInternalServerError, fmt.Errorf("failed to process token lookup response: %w", err))
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		defer resp.Response.Body.Close()
 | |
| 
 | |
| 		metrics.IncrCounter([]string{"agent", "proxy", "success"}, 1)
 | |
| 		if resp.CacheMeta != nil {
 | |
| 			if resp.CacheMeta.Hit {
 | |
| 				metrics.IncrCounter([]string{"agent", "cache", "hit"}, 1)
 | |
| 			} else {
 | |
| 				metrics.IncrCounter([]string{"agent", "cache", "miss"}, 1)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Set headers
 | |
| 		setHeaders(w, resp)
 | |
| 
 | |
| 		// Set response body
 | |
| 		io.Copy(w, resp.Response.Body)
 | |
| 		return
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // setHeaders is a helper that sets the header values based on SendResponse. It
 | |
| // copies over the headers from the original response and also includes any
 | |
| // cache-related headers.
 | |
| func setHeaders(w http.ResponseWriter, resp *SendResponse) {
 | |
| 	// Set header values
 | |
| 	copyHeader(w.Header(), resp.Response.Header)
 | |
| 	if resp.CacheMeta != nil {
 | |
| 		xCacheVal := "MISS"
 | |
| 
 | |
| 		if resp.CacheMeta.Hit {
 | |
| 			xCacheVal = "HIT"
 | |
| 
 | |
| 			// If this is a cache hit, we also set the Age header
 | |
| 			age := fmt.Sprintf("%.0f", resp.CacheMeta.Age.Seconds())
 | |
| 			w.Header().Set("Age", age)
 | |
| 
 | |
| 			// Update the date value
 | |
| 			w.Header().Set("Date", time.Now().Format(http.TimeFormat))
 | |
| 		}
 | |
| 
 | |
| 		w.Header().Set("X-Cache", xCacheVal)
 | |
| 	}
 | |
| 
 | |
| 	// Set status code
 | |
| 	w.WriteHeader(resp.Response.StatusCode)
 | |
| }
 | |
| 
 | |
| // sanitizeAutoAuthTokenResponse checks if the request was a lookup or renew
 | |
| // and 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 sanitizeAutoAuthTokenResponse(ctx context.Context, logger hclog.Logger, inmemSink sink.Sink, req *SendRequest, resp *SendResponse) error {
 | |
| 	// If auto-auth token is not being used, there is nothing to do.
 | |
| 	if inmemSink == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	autoAuthToken := inmemSink.(sink.SinkReader).Token()
 | |
| 
 | |
| 	// If lookup responded with non 200 status, there is nothing to do.
 | |
| 	if resp.Response.StatusCode != http.StatusOK {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	_, path := deriveNamespaceAndRevocationPath(req)
 | |
| 	switch path {
 | |
| 	case vaultPathTokenLookupSelf, vaultPathTokenRenewSelf:
 | |
| 		if req.Token != autoAuthToken {
 | |
| 			return nil
 | |
| 		}
 | |
| 	case vaultPathTokenLookup, vaultPathTokenRenew:
 | |
| 		jsonBody := map[string]interface{}{}
 | |
| 		if err := json.Unmarshal(req.RequestBody, &jsonBody); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		tokenRaw, ok := jsonBody["token"]
 | |
| 		if !ok {
 | |
| 			// Input error will be caught by the API
 | |
| 			return nil
 | |
| 		}
 | |
| 		token, ok := tokenRaw.(string)
 | |
| 		if !ok {
 | |
| 			// Input error will be caught by the API
 | |
| 			return nil
 | |
| 		}
 | |
| 		if token != "" && token != autoAuthToken {
 | |
| 			// Lookup is performed on the non-auto-auth token
 | |
| 			return nil
 | |
| 		}
 | |
| 	default:
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	logger.Info("stripping auto-auth token from the response", "method", req.Request.Method, "path", req.Request.URL.Path)
 | |
| 	secret, err := api.ParseSecret(bytes.NewReader(resp.ResponseBody))
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("failed to parse token lookup response: %v", err)
 | |
| 	}
 | |
| 	if secret == nil {
 | |
| 		return nil
 | |
| 	} else if secret.Data != nil {
 | |
| 		// lookup endpoints
 | |
| 		if secret.Data["id"] == nil && secret.Data["accessor"] == nil {
 | |
| 			return nil
 | |
| 		}
 | |
| 		delete(secret.Data, "id")
 | |
| 		delete(secret.Data, "accessor")
 | |
| 	} else if secret.Auth != nil {
 | |
| 		// renew endpoints
 | |
| 		if secret.Auth.Accessor == "" && secret.Auth.ClientToken == "" {
 | |
| 			return nil
 | |
| 		}
 | |
| 		secret.Auth.Accessor = ""
 | |
| 		secret.Auth.ClientToken = ""
 | |
| 	} else {
 | |
| 		// nothing to redact
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	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.NewReader(bodyBytes))
 | |
| 	resp.Response.ContentLength = int64(len(bodyBytes))
 | |
| 
 | |
| 	// Serialize and re-read the response
 | |
| 	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)
 | |
| 		}
 | |
| 	}
 | |
| }
 |