Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customizing HTTP headers in the config file #12485

Merged
merged 26 commits into from Oct 13, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5bf301c
Customizing HTTP headers in the config file
hghaf099 Sep 2, 2021
9dfa689
Add changelog, fix bad imports
hghaf099 Sep 3, 2021
65f3d3a
fixing some bugs
hghaf099 Sep 3, 2021
61c12eb
fixing interaction of custom headers and /ui
hghaf099 Sep 3, 2021
f4232cd
Defining a member in core to set custom response headers
hghaf099 Sep 5, 2021
5ec2510
missing additional file
hghaf099 Sep 5, 2021
6982b8a
Some refactoring
hghaf099 Sep 7, 2021
f804b26
Adding automated tests for the feature
hghaf099 Sep 8, 2021
e52db45
Changing some error messages based on some recommendations
hghaf099 Sep 9, 2021
06ef62f
Incorporating custom response headers struct into the request context
hghaf099 Sep 14, 2021
ad93253
removing some unused references
hghaf099 Sep 14, 2021
0905ab2
fixing a test
hghaf099 Sep 14, 2021
33a4aa5
changing some error messages, removing a default header value from /ui
hghaf099 Sep 14, 2021
92867b4
fixing a test
hghaf099 Sep 14, 2021
2ae11ea
wrapping ResponseWriter to set the custom headers
hghaf099 Sep 16, 2021
434d8cb
adding a new test
hghaf099 Sep 16, 2021
6a106f0
some cleanup
hghaf099 Sep 16, 2021
7881067
removing some extra lines
hghaf099 Sep 16, 2021
e266713
Addressing comments
hghaf099 Sep 21, 2021
879d489
fixing some agent tests
hghaf099 Sep 21, 2021
2b7dd50
skipping custom headers from agent listener config,
hghaf099 Sep 22, 2021
0964ef8
Removing default custom headers, and renaming some function varibles
hghaf099 Oct 5, 2021
96cb6df
Merge branch 'main' into hghaf099-VAULT-3190-Parsing-Custom-HTTP-Headers
hghaf099 Oct 5, 2021
b6eedd1
some refacotring
hghaf099 Oct 7, 2021
08d0157
Refactoring and addressing comments
hghaf099 Oct 8, 2021
5734ab1
removing a function and fixing comments
hghaf099 Oct 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/12485.txt
@@ -0,0 +1,3 @@
```release-note:feature
http: Enable users to customize HTTP response headers
hghaf099 marked this conversation as resolved.
Show resolved Hide resolved
```
7 changes: 4 additions & 3 deletions command/agent.go
Expand Up @@ -878,9 +878,10 @@ func (c *AgentCommand) Run(args []string) int {
func verifyRequestHeader(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if val, ok := r.Header[consts.RequestHeaderName]; !ok || len(val) != 1 || val[0] != "true" {
logical.RespondError(w,
http.StatusPreconditionFailed,
errors.New(fmt.Sprintf("missing '%s' header", consts.RequestHeaderName)))
err := errors.New(fmt.Sprintf("missing '%s' header", consts.RequestHeaderName))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be fmt.Errorf() since you're doing a format string

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

status := http.StatusPreconditionFailed
logical.AdjustErrorStatusCode(&status, err)
logical.RespondError(w, status, err)
hghaf099 marked this conversation as resolved.
Show resolved Hide resolved
return
}

Expand Down
17 changes: 13 additions & 4 deletions command/agent/cache/handler.go
Expand Up @@ -37,8 +37,11 @@ func Handler(ctx context.Context, logger hclog.Logger, proxier Proxier, inmemSin
// Parse and reset body.
reqBody, err := ioutil.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"))
errRet := errors.New("failed to read request body")
ncabatoff marked this conversation as resolved.
Show resolved Hide resolved
logger.Error(errRet.Error())
status := http.StatusInternalServerError
logical.AdjustErrorStatusCode(&status, errRet)
logical.RespondError(w, status, errRet)
return
}
if r.Body != nil {
Expand All @@ -59,14 +62,20 @@ func Handler(ctx context.Context, logger hclog.Logger, proxier Proxier, inmemSin
w.WriteHeader(resp.Response.StatusCode)
io.Copy(w, resp.Response.Body)
} else {
logical.RespondError(w, http.StatusInternalServerError, fmt.Errorf("failed to get the response: %w", err))
status := http.StatusInternalServerError
errNew := fmt.Errorf("failed to get the response: %w", err)
logical.AdjustErrorStatusCode(&status, errNew)
logical.RespondError(w, status, errNew)
}
return
}

err = processTokenLookupResponse(ctx, logger, inmemSink, req, resp)
if err != nil {
logical.RespondError(w, http.StatusInternalServerError, fmt.Errorf("failed to process token lookup response: %w", err))
status := http.StatusInternalServerError
errNew := fmt.Errorf("failed to process token lookup response: %w", err)
logical.AdjustErrorStatusCode(&status, errNew)
logical.RespondError(w, status, errNew)
return
}

Expand Down
14 changes: 11 additions & 3 deletions command/agent/cache/lease_cache.go
Expand Up @@ -576,7 +576,10 @@ func (c *LeaseCache) HandleCacheClear(ctx context.Context) http.Handler {
if err == io.EOF {
err = errors.New("empty JSON provided")
}
logical.RespondError(w, http.StatusBadRequest, fmt.Errorf("failed to parse JSON input: %w", err))
status := http.StatusBadRequest
errNew := fmt.Errorf("failed to parse JSON input: %w", err)
logical.AdjustErrorStatusCode(&status, errNew)
logical.RespondError(w, status, errNew)
return
}

Expand All @@ -585,7 +588,10 @@ func (c *LeaseCache) HandleCacheClear(ctx context.Context) http.Handler {
in, err := parseCacheClearInput(req)
if err != nil {
c.logger.Error("unable to parse clear input", "error", err)
logical.RespondError(w, http.StatusBadRequest, fmt.Errorf("failed to parse clear input: %w", err))
status := http.StatusBadRequest
errNew := fmt.Errorf("failed to parse clear input: %w", err)
logical.AdjustErrorStatusCode(&status, errNew)
logical.RespondError(w, status, errNew)
return
}

Expand All @@ -596,7 +602,9 @@ func (c *LeaseCache) HandleCacheClear(ctx context.Context) http.Handler {
if err == errInvalidType {
httpStatus = http.StatusBadRequest
}
logical.RespondError(w, httpStatus, fmt.Errorf("failed to clear cache: %w", err))
errNew := fmt.Errorf("failed to clear cache: %w", err)
logical.AdjustErrorStatusCode(&httpStatus, errNew)
logical.RespondError(w, httpStatus, errNew)
return
}

Expand Down
8 changes: 7 additions & 1 deletion command/server.go
Expand Up @@ -1541,6 +1541,12 @@ func (c *ServerCommand) Run(args []string) int {

core.SetConfig(config)

// reloading custom response headers to make sure we have
// the most up to date headers after reloading the config file
if err = core.ReloadCustomResponseHeaders(); err != nil {
c.UI.Error(err.Error())
ncabatoff marked this conversation as resolved.
Show resolved Hide resolved
}

if config.LogLevel != "" {
configLogLevel := strings.ToLower(strings.TrimSpace(config.LogLevel))
switch configLogLevel {
Expand Down Expand Up @@ -2631,7 +2637,7 @@ func startHttpServers(c *ServerCommand, core *vault.Core, config *server.Config,
})

if len(ln.Config.XForwardedForAuthorizedAddrs) > 0 {
handler = vaulthttp.WrapForwardedForHandler(handler, ln.Config)
handler = vaulthttp.WrapForwardedForHandler(handler, ln.Config, core.SetCustomResponseHeaders)
}

// server defaults
Expand Down
60 changes: 60 additions & 0 deletions command/server/config_custom_response_headers_test.go
@@ -0,0 +1,60 @@
package server

import (
"github.com/go-test/deep"
"testing"
)

var defaultCustomHeaders = map[string]string {
"Strict-Transport-Security": "max-age=1; domains",
"Content-Security-Policy": "default-src 'others'",
"X-Vault-Ignored": "ignored",
"X-Custom-Header": "Custom header value default",
"X-Frame-Options": "Deny",
"X-Content-Type-Options": "nosniff",
"Content-Type": "text/plain; charset=utf-8",
"X-XSS-Protection": "1; mode=block",
}

var customHeaders307 = map[string]string {
"X-Custom-Header": "Custom header value 307",
}

var customHeader3xx = map[string]string {
"X-Vault-Ignored-3xx": "Ignored 3xx",
"X-Custom-Header": "Custom header value 3xx",
}

var customHeaders200 = map[string]string {
"Someheader-200": "200",
"X-Custom-Header": "Custom header value 200",
}

var customHeader2xx = map[string]string {
"X-Custom-Header": "Custom header value 2xx",
}

var customHeader400 = map[string]string {
"Someheader-400": "400",
}

func TestCustomResponseHeadersConfigs(t *testing.T) {
expectedCustomResponseHeader := map[string]map[string]string {
"default": defaultCustomHeaders,
"307": customHeaders307,
"3xx": customHeader3xx,
"200": customHeaders200,
"2xx": customHeader2xx,
"400": customHeader400,
}

config, err := LoadConfigFile("./test-fixtures/config_custom_response_headers_1.hcl")

if err != nil {
t.Fatalf("Error encountered when loading config %+v", err)
}
if diff := deep.Equal(expectedCustomResponseHeader, config.Listeners[0].CustomResponseHeaders); diff != nil {
t.Fatalf("parsed custom headers do not match the expected ones")
}
}

31 changes: 31 additions & 0 deletions command/server/test-fixtures/config_custom_response_headers_1.hcl
@@ -0,0 +1,31 @@
storage "inmem" {}
listener "tcp" {
address = "127.0.0.1:8200"
tls_disable = true
custom_response_headers {
"default" = {
"Strict-Transport-Security" = ["max-age=1","domains"],
"Content-Security-Policy" = ["default-src 'others'"],
"X-Vault-Ignored" = ["ignored"],
"X-Custom-Header" = ["Custom header value default"],
}
"307" = {
"X-Custom-Header" = ["Custom header value 307"],
}
"3xx" = {
"X-Vault-Ignored-3xx" = ["Ignored 3xx"],
"X-Custom-Header" = ["Custom header value 3xx"]
}
"200" = {
"someheader-200" = ["200"],
"X-Custom-Header" = ["Custom header value 200"]
}
"2xx" = {
"X-Custom-Header" = ["Custom header value 2xx"]
}
"400" = {
"someheader-400" = ["400"]
}
}
}
disable_mlock = true
6 changes: 4 additions & 2 deletions http/cors.go
Expand Up @@ -40,12 +40,14 @@ func wrapCORSHandler(h http.Handler, core *vault.Core) http.Handler {

// Return a 403 if the origin is not allowed to make cross-origin requests.
if !corsConf.IsValidOrigin(origin) {
respondError(w, http.StatusForbidden, fmt.Errorf("origin not allowed"))
respondError(w, http.StatusForbidden, fmt.Errorf("origin not allowed"), core.SetCustomResponseHeaders)
return
}

if req.Method == http.MethodOptions && !strutil.StrListContains(allowedMethods, requestMethod) {
w.WriteHeader(http.StatusMethodNotAllowed)
status := http.StatusMethodNotAllowed
core.SetCustomResponseHeaders(w, status)
w.WriteHeader(status)
return
}

Expand Down
12 changes: 6 additions & 6 deletions http/forwarded_for_test.go
Expand Up @@ -42,7 +42,7 @@ func TestHandler_XForwardedFor(t *testing.T) {
})
listenerConfig := getListenerConfigForMarshalerTest(goodAddr)
listenerConfig.XForwardedForRejectNotPresent = true
return WrapForwardedForHandler(origHandler, listenerConfig)
return WrapForwardedForHandler(origHandler, listenerConfig, nil)
}

cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
Expand Down Expand Up @@ -85,7 +85,7 @@ func TestHandler_XForwardedFor(t *testing.T) {
})
listenerConfig := getListenerConfigForMarshalerTest(badAddr)
listenerConfig.XForwardedForRejectNotPresent = true
return WrapForwardedForHandler(origHandler, listenerConfig)
return WrapForwardedForHandler(origHandler, listenerConfig, nil)
}

cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
Expand Down Expand Up @@ -121,7 +121,7 @@ func TestHandler_XForwardedFor(t *testing.T) {
listenerConfig := getListenerConfigForMarshalerTest(badAddr)
listenerConfig.XForwardedForRejectNotPresent = true
listenerConfig.XForwardedForRejectNotAuthorized = true
return WrapForwardedForHandler(origHandler, listenerConfig)
return WrapForwardedForHandler(origHandler, listenerConfig, nil)
}

cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
Expand Down Expand Up @@ -155,7 +155,7 @@ func TestHandler_XForwardedFor(t *testing.T) {
listenerConfig.XForwardedForRejectNotPresent = true
listenerConfig.XForwardedForRejectNotAuthorized = true
listenerConfig.XForwardedForHopSkips = 4
return WrapForwardedForHandler(origHandler, listenerConfig)
return WrapForwardedForHandler(origHandler, listenerConfig, nil)
}

cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
Expand Down Expand Up @@ -189,7 +189,7 @@ func TestHandler_XForwardedFor(t *testing.T) {
listenerConfig.XForwardedForRejectNotPresent = true
listenerConfig.XForwardedForRejectNotAuthorized = true
listenerConfig.XForwardedForHopSkips = 1
return WrapForwardedForHandler(origHandler, listenerConfig)
return WrapForwardedForHandler(origHandler, listenerConfig, nil)
}

cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
Expand Down Expand Up @@ -226,7 +226,7 @@ func TestHandler_XForwardedFor(t *testing.T) {
listenerConfig.XForwardedForRejectNotPresent = true
listenerConfig.XForwardedForRejectNotAuthorized = true
listenerConfig.XForwardedForHopSkips = 1
return WrapForwardedForHandler(origHandler, listenerConfig)
return WrapForwardedForHandler(origHandler, listenerConfig, nil)
}

cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
Expand Down