mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-13 18:17:02 +02:00
* Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License. Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUS-1.1 * Fix test that expected exact offset on hcl file --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Co-authored-by: Sarah Thompson <sthompson@hashicorp.com> Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
233 lines
6.3 KiB
Go
233 lines
6.3 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
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/agentproxyshared/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)
|
|
}
|
|
}
|
|
}
|