mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	Passthrough request headers (#4172)
* Add passthrough request headers for secret/auth mounts * Update comments * Fix SyncCache deletion of passthrough_request_headers * Remove debug line * Case-insensitive header comparison * Remove unnecessary allocation * Short-circuit filteredPassthroughHeaders if there's nothing to filter * Add whitelistedHeaders list * Update router logic after merge * Add whitelist test * Add lowercase x-vault-kv-client to whitelist * Add back const * Refactor whitelist logic
This commit is contained in:
		 Calvin Leung Huang
					Calvin Leung Huang
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							a03a722fa9
						
					
				
				
					commit
					c54c9519c8
				
			| @@ -98,6 +98,7 @@ type AuthConfigInput struct { | ||||
| 	AuditNonHMACRequestKeys   []string `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"` | ||||
| 	AuditNonHMACResponseKeys  []string `json:"audit_non_hmac_response_keys,omitempty" structs:"audit_non_hmac_response_keys" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         string   `json:"listing_visibility,omitempty" structs:"listing_visibility" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" structs:"passthrough_request_headers" mapstructure:"passthrough_request_headers"` | ||||
| } | ||||
|  | ||||
| type AuthMount struct { | ||||
| @@ -117,4 +118,5 @@ type AuthConfigOutput struct { | ||||
| 	AuditNonHMACRequestKeys   []string `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"` | ||||
| 	AuditNonHMACResponseKeys  []string `json:"audit_non_hmac_response_keys,omitempty" structs:"audit_non_hmac_response_keys" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         string   `json:"listing_visibility,omitempty" structs:"listing_visibility" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" structs:"passthrough_request_headers" mapstructure:"passthrough_request_headers"` | ||||
| } | ||||
|   | ||||
| @@ -138,6 +138,7 @@ type MountConfigInput struct { | ||||
| 	AuditNonHMACRequestKeys   []string          `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"` | ||||
| 	AuditNonHMACResponseKeys  []string          `json:"audit_non_hmac_response_keys,omitempty" structs:"audit_non_hmac_response_keys" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         string            `json:"listing_visibility,omitempty" structs:"listing_visibility" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string          `json:"passthrough_request_headers,omitempty" structs:"passthrough_request_headers" mapstructure:"passthrough_request_headers"` | ||||
| } | ||||
|  | ||||
| type MountOutput struct { | ||||
| @@ -159,4 +160,5 @@ type MountConfigOutput struct { | ||||
| 	AuditNonHMACRequestKeys   []string          `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"` | ||||
| 	AuditNonHMACResponseKeys  []string          `json:"audit_non_hmac_response_keys,omitempty" structs:"audit_non_hmac_response_keys" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         string            `json:"listing_visibility,omitempty" structs:"listing_visibility" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string          `json:"passthrough_request_headers,omitempty" structs:"passthrough_request_headers" mapstructure:"passthrough_request_headers"` | ||||
| } | ||||
|   | ||||
| @@ -24,6 +24,7 @@ type AuthEnableCommand struct { | ||||
| 	flagAuditNonHMACRequestKeys   []string | ||||
| 	flagAuditNonHMACResponseKeys  []string | ||||
| 	flagListingVisibility         string | ||||
| 	flagPassthroughRequestHeaders []string | ||||
| 	flagPluginName                string | ||||
| 	flagOptions                   map[string]string | ||||
| 	flagLocal                     bool | ||||
| @@ -121,6 +122,13 @@ func (c *AuthEnableCommand) Flags() *FlagSets { | ||||
| 		Usage:  "Determines the visibility of the mount in the UI-specific listing endpoint.", | ||||
| 	}) | ||||
|  | ||||
| 	f.StringSliceVar(&StringSliceVar{ | ||||
| 		Name:   flagNamePassthroughRequestHeaders, | ||||
| 		Target: &c.flagPassthroughRequestHeaders, | ||||
| 		Usage: "Comma-separated string or list of request header values that " + | ||||
| 			"will be sent to the backend", | ||||
| 	}) | ||||
|  | ||||
| 	f.StringVar(&StringVar{ | ||||
| 		Name:       "plugin-name", | ||||
| 		Target:     &c.flagPluginName, | ||||
| @@ -229,6 +237,10 @@ func (c *AuthEnableCommand) Run(args []string) int { | ||||
| 		if fl.Name == flagNameListingVisibility { | ||||
| 			authOpts.Config.ListingVisibility = c.flagListingVisibility | ||||
| 		} | ||||
|  | ||||
| 		if fl.Name == flagNamePassthroughRequestHeaders { | ||||
| 			authOpts.Config.PassthroughRequestHeaders = c.flagPassthroughRequestHeaders | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	if err := client.Sys().EnableAuthWithOptions(authPath, authOpts); err != nil { | ||||
|   | ||||
| @@ -79,6 +79,8 @@ const ( | ||||
| 	flagNameAuditNonHMACResponseKeys = "audit-non-hmac-response-keys" | ||||
| 	// flagListingVisibility is the flag to toggle whether to show the mount in the UI-specific listing endpoint | ||||
| 	flagNameListingVisibility = "listing-visibility" | ||||
| 	// flagNamePassthroughRequestHeaders is the flag name used to set passthrough request headers to the backend | ||||
| 	flagNamePassthroughRequestHeaders = "passthrough-request-headers" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
|   | ||||
| @@ -24,6 +24,7 @@ type SecretsEnableCommand struct { | ||||
| 	flagAuditNonHMACRequestKeys   []string | ||||
| 	flagAuditNonHMACResponseKeys  []string | ||||
| 	flagListingVisibility         string | ||||
| 	flagPassthroughRequestHeaders []string | ||||
| 	flagForceNoCache              bool | ||||
| 	flagPluginName                string | ||||
| 	flagOptions                   map[string]string | ||||
| @@ -129,6 +130,13 @@ func (c *SecretsEnableCommand) Flags() *FlagSets { | ||||
| 		Usage:  "Determines the visibility of the mount in the UI-specific listing endpoint.", | ||||
| 	}) | ||||
|  | ||||
| 	f.StringSliceVar(&StringSliceVar{ | ||||
| 		Name:   flagNamePassthroughRequestHeaders, | ||||
| 		Target: &c.flagPassthroughRequestHeaders, | ||||
| 		Usage: "Comma-separated string or list of request header values that " + | ||||
| 			"will be sent to the backend", | ||||
| 	}) | ||||
|  | ||||
| 	f.BoolVar(&BoolVar{ | ||||
| 		Name:    "force-no-cache", | ||||
| 		Target:  &c.flagForceNoCache, | ||||
| @@ -249,6 +257,10 @@ func (c *SecretsEnableCommand) Run(args []string) int { | ||||
| 		if fl.Name == flagNameListingVisibility { | ||||
| 			mountInput.Config.ListingVisibility = c.flagListingVisibility | ||||
| 		} | ||||
|  | ||||
| 		if fl.Name == flagNamePassthroughRequestHeaders { | ||||
| 			mountInput.Config.PassthroughRequestHeaders = c.flagPassthroughRequestHeaders | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	if err := client.Sys().Mount(mountPath, mountInput); err != nil { | ||||
|   | ||||
| @@ -1287,6 +1287,7 @@ func TestSysTuneMount_nonHMACKeys(t *testing.T) { | ||||
| 	resp = testHttpPost(t, token, addr+"/v1/sys/mounts/secret/tune", map[string]interface{}{ | ||||
| 		"audit_non_hmac_response_keys": "", | ||||
| 	}) | ||||
| 	testResponseStatus(t, resp, 204) | ||||
|  | ||||
| 	// Check results | ||||
| 	resp = testHttpGet(t, token, addr+"/v1/sys/mounts/secret/tune") | ||||
| @@ -1318,7 +1319,7 @@ func TestSysTuneMount_nonHMACKeys(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSysTuneMount_showUIMount(t *testing.T) { | ||||
| func TestSysTuneMount_listingVisibility(t *testing.T) { | ||||
| 	core, _, token := vault.TestCoreUnsealed(t) | ||||
| 	ln, addr := TestServer(t, core) | ||||
| 	defer ln.Close() | ||||
| @@ -1390,3 +1391,78 @@ func TestSysTuneMount_showUIMount(t *testing.T) { | ||||
| 		t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, actual) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSysTuneMount_passthroughRequestHeaders(t *testing.T) { | ||||
| 	core, _, token := vault.TestCoreUnsealed(t) | ||||
| 	ln, addr := TestServer(t, core) | ||||
| 	defer ln.Close() | ||||
| 	TestServerAuth(t, addr, token) | ||||
|  | ||||
| 	// Mount-tune the audit_non_hmac_request_keys | ||||
| 	resp := testHttpPost(t, token, addr+"/v1/sys/mounts/secret/tune", map[string]interface{}{ | ||||
| 		"passthrough_request_headers": "X-Vault-Foo", | ||||
| 	}) | ||||
| 	testResponseStatus(t, resp, 204) | ||||
|  | ||||
| 	// Check results | ||||
| 	resp = testHttpGet(t, token, addr+"/v1/sys/mounts/secret/tune") | ||||
| 	testResponseStatus(t, resp, 200) | ||||
|  | ||||
| 	actual := map[string]interface{}{} | ||||
| 	expected := map[string]interface{}{ | ||||
| 		"lease_id":       "", | ||||
| 		"renewable":      false, | ||||
| 		"lease_duration": json.Number("0"), | ||||
| 		"wrap_info":      nil, | ||||
| 		"warnings":       nil, | ||||
| 		"auth":           nil, | ||||
| 		"data": map[string]interface{}{ | ||||
| 			"default_lease_ttl":           json.Number("2764800"), | ||||
| 			"max_lease_ttl":               json.Number("2764800"), | ||||
| 			"force_no_cache":              false, | ||||
| 			"passthrough_request_headers": []interface{}{"X-Vault-Foo"}, | ||||
| 		}, | ||||
| 		"default_lease_ttl":           json.Number("2764800"), | ||||
| 		"max_lease_ttl":               json.Number("2764800"), | ||||
| 		"force_no_cache":              false, | ||||
| 		"passthrough_request_headers": []interface{}{"X-Vault-Foo"}, | ||||
| 	} | ||||
| 	testResponseBody(t, resp, &actual) | ||||
| 	expected["request_id"] = actual["request_id"] | ||||
| 	if !reflect.DeepEqual(actual, expected) { | ||||
| 		t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, actual) | ||||
| 	} | ||||
|  | ||||
| 	// Unset the mount tune value | ||||
| 	resp = testHttpPost(t, token, addr+"/v1/sys/mounts/secret/tune", map[string]interface{}{ | ||||
| 		"passthrough_request_headers": "", | ||||
| 	}) | ||||
| 	testResponseStatus(t, resp, 204) | ||||
|  | ||||
| 	// Check results | ||||
| 	resp = testHttpGet(t, token, addr+"/v1/sys/mounts/secret/tune") | ||||
| 	testResponseStatus(t, resp, 200) | ||||
|  | ||||
| 	actual = map[string]interface{}{} | ||||
| 	expected = map[string]interface{}{ | ||||
| 		"lease_id":       "", | ||||
| 		"renewable":      false, | ||||
| 		"lease_duration": json.Number("0"), | ||||
| 		"wrap_info":      nil, | ||||
| 		"warnings":       nil, | ||||
| 		"auth":           nil, | ||||
| 		"data": map[string]interface{}{ | ||||
| 			"default_lease_ttl": json.Number("2764800"), | ||||
| 			"max_lease_ttl":     json.Number("2764800"), | ||||
| 			"force_no_cache":    false, | ||||
| 		}, | ||||
| 		"default_lease_ttl": json.Number("2764800"), | ||||
| 		"max_lease_ttl":     json.Number("2764800"), | ||||
| 		"force_no_cache":    false, | ||||
| 	} | ||||
| 	testResponseBody(t, resp, &actual) | ||||
| 	expected["request_id"] = actual["request_id"] | ||||
| 	if !reflect.DeepEqual(actual, expected) { | ||||
| 		t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, actual) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -2276,3 +2276,88 @@ func TestCore_Standby_Rotate(t *testing.T) { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Ensure that InternalData is never returned | ||||
| func TestCore_HandleRequest_Headers(t *testing.T) { | ||||
| 	noop := &NoopBackend{ | ||||
| 		Response: &logical.Response{ | ||||
| 			Data: map[string]interface{}{}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	c, _, root := TestCoreUnsealed(t) | ||||
| 	c.logicalBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) { | ||||
| 		return noop, nil | ||||
| 	} | ||||
|  | ||||
| 	// Enable the backend | ||||
| 	req := logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo") | ||||
| 	req.Data["type"] = "noop" | ||||
| 	req.ClientToken = root | ||||
| 	_, err := c.HandleRequest(req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// Mount tune | ||||
| 	req = logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/foo/tune") | ||||
| 	req.Data["passthrough_request_headers"] = []string{"Should-Passthrough", "should-passthrough-case-insensitive"} | ||||
| 	req.ClientToken = root | ||||
| 	_, err = c.HandleRequest(req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// Attempt to read | ||||
| 	lreq := &logical.Request{ | ||||
| 		Operation:   logical.ReadOperation, | ||||
| 		Path:        "foo/test", | ||||
| 		ClientToken: root, | ||||
| 		Headers: map[string][]string{ | ||||
| 			"X-Vault-Kv-Client":                   []string{"foo"}, | ||||
| 			"Should-Passthrough":                  []string{"foo"}, | ||||
| 			"Should-Passthrough-Case-Insensitive": []string{"baz"}, | ||||
| 			"Should-Not-Passthrough":              []string{"bar"}, | ||||
| 		}, | ||||
| 	} | ||||
| 	_, err = c.HandleRequest(lreq) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// Check the headers | ||||
| 	headers := noop.Requests[0].Headers | ||||
|  | ||||
| 	// Test whitelisted values | ||||
| 	if val, ok := headers["X-Vault-Kv-Client"]; ok { | ||||
| 		expected := []string{"foo"} | ||||
| 		if !reflect.DeepEqual(val, expected) { | ||||
| 			t.Fatalf("expected: %v, got: %v", expected, val) | ||||
| 		} | ||||
| 	} else { | ||||
| 		t.Fatalf("expected 'X-Vault-Kv-Client' to be present in the headers map") | ||||
| 	} | ||||
|  | ||||
| 	// Test passthrough values | ||||
| 	if val, ok := headers["Should-Passthrough"]; ok { | ||||
| 		expected := []string{"foo"} | ||||
| 		if !reflect.DeepEqual(val, expected) { | ||||
| 			t.Fatalf("expected: %v, got: %v", expected, val) | ||||
| 		} | ||||
| 	} else { | ||||
| 		t.Fatalf("expected 'Should-Passthrough' to be present in the headers map") | ||||
| 	} | ||||
|  | ||||
| 	if val, ok := headers["Should-Passthrough-Case-Insensitive"]; ok { | ||||
| 		expected := []string{"baz"} | ||||
| 		if !reflect.DeepEqual(val, expected) { | ||||
| 			t.Fatalf("expected: %v, got: %v", expected, val) | ||||
| 		} | ||||
| 	} else { | ||||
| 		t.Fatalf("expected 'Should-Passthrough-Case-Insensitive' to be present in the headers map") | ||||
| 	} | ||||
|  | ||||
| 	if _, ok := headers["Should-Not-Passthrough"]; ok { | ||||
| 		t.Fatalf("did not expect 'Should-Not-Passthrough' to be in the headers map") | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -275,6 +275,10 @@ func NewSystemBackend(core *Core) *SystemBackend { | ||||
| 						Type:        framework.TypeString, | ||||
| 						Description: strings.TrimSpace(sysHelp["listing_visibility"][0]), | ||||
| 					}, | ||||
| 					"passthrough_request_headers": &framework.FieldSchema{ | ||||
| 						Type:        framework.TypeCommaStringSlice, | ||||
| 						Description: strings.TrimSpace(sysHelp["passthrough_request_headers"][0]), | ||||
| 					}, | ||||
| 				}, | ||||
| 				Callbacks: map[logical.Operation]framework.OperationFunc{ | ||||
| 					logical.ReadOperation:   b.handleAuthTuneRead, | ||||
| @@ -320,6 +324,10 @@ func NewSystemBackend(core *Core) *SystemBackend { | ||||
| 						Type:        framework.TypeString, | ||||
| 						Description: strings.TrimSpace(sysHelp["listing_visibility"][0]), | ||||
| 					}, | ||||
| 					"passthrough_request_headers": &framework.FieldSchema{ | ||||
| 						Type:        framework.TypeCommaStringSlice, | ||||
| 						Description: strings.TrimSpace(sysHelp["passthrough_request_headers"][0]), | ||||
| 					}, | ||||
| 				}, | ||||
|  | ||||
| 				Callbacks: map[logical.Operation]framework.OperationFunc{ | ||||
| @@ -1498,6 +1506,10 @@ func (b *SystemBackend) handleMountTable(ctx context.Context, req *logical.Reque | ||||
| 		if len(entry.Config.ListingVisibility) > 0 { | ||||
| 			entryConfig["listing_visibility"] = entry.Config.ListingVisibility | ||||
| 		} | ||||
| 		if rawVal, ok := entry.synthesizedConfigCache.Load("passthrough_request_headers"); ok { | ||||
| 			entryConfig["passthrough_request_headers"] = rawVal.([]string) | ||||
| 		} | ||||
|  | ||||
| 		info["config"] = entryConfig | ||||
| 		resp.Data[entry.Path] = info | ||||
| 	} | ||||
| @@ -1612,6 +1624,9 @@ func (b *SystemBackend) handleMount(ctx context.Context, req *logical.Request, d | ||||
| 	if len(apiConfig.AuditNonHMACResponseKeys) > 0 { | ||||
| 		config.AuditNonHMACResponseKeys = apiConfig.AuditNonHMACResponseKeys | ||||
| 	} | ||||
| 	if len(apiConfig.PassthroughRequestHeaders) > 0 { | ||||
| 		config.PassthroughRequestHeaders = apiConfig.PassthroughRequestHeaders | ||||
| 	} | ||||
|  | ||||
| 	// Create the mount entry | ||||
| 	me := &MountEntry{ | ||||
| @@ -1782,6 +1797,10 @@ func (b *SystemBackend) handleTuneReadCommon(path string) (*logical.Response, er | ||||
| 		resp.Data["listing_visibility"] = mountEntry.Config.ListingVisibility | ||||
| 	} | ||||
|  | ||||
| 	if rawVal, ok := mountEntry.synthesizedConfigCache.Load("passthrough_request_headers"); ok { | ||||
| 		resp.Data["passthrough_request_headers"] = rawVal.([]string) | ||||
| 	} | ||||
|  | ||||
| 	if len(mountEntry.Options) > 0 { | ||||
| 		resp.Data["options"] = mountEntry.Options | ||||
| 	} | ||||
| @@ -2000,6 +2019,32 @@ func (b *SystemBackend) handleTuneWriteCommon(ctx context.Context, path string, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if rawVal, ok := data.GetOk("passthrough_request_headers"); ok { | ||||
| 		headers := rawVal.([]string) | ||||
|  | ||||
| 		oldVal := mountEntry.Config.PassthroughRequestHeaders | ||||
| 		mountEntry.Config.PassthroughRequestHeaders = headers | ||||
|  | ||||
| 		// Update the mount table | ||||
| 		var err error | ||||
| 		switch { | ||||
| 		case strings.HasPrefix(path, "auth/"): | ||||
| 			err = b.Core.persistAuth(ctx, b.Core.auth, mountEntry.Local) | ||||
| 		default: | ||||
| 			err = b.Core.persistMounts(ctx, b.Core.mounts, mountEntry.Local) | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			mountEntry.Config.PassthroughRequestHeaders = oldVal | ||||
| 			return handleError(err) | ||||
| 		} | ||||
|  | ||||
| 		mountEntry.SyncCache() | ||||
|  | ||||
| 		if b.Core.logger.IsInfo() { | ||||
| 			b.Core.logger.Info("core: mount tuning of passthrough_request_headers successful", "path", path) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	var resp *logical.Response | ||||
| 	if optionsRaw, ok := data.GetOk("options"); ok { | ||||
| 		b.Core.logger.Info("core: mount tuning of options", "path", path) | ||||
| @@ -2050,6 +2095,7 @@ func (b *SystemBackend) handleTuneWriteCommon(ctx context.Context, path string, | ||||
|  | ||||
| 		delete(mountEntry.Options, "upgrade") | ||||
| 	} | ||||
|  | ||||
| 	return resp, nil | ||||
| } | ||||
|  | ||||
| @@ -2214,6 +2260,10 @@ func (b *SystemBackend) handleAuthTable(ctx context.Context, req *logical.Reques | ||||
| 		if len(entry.Config.ListingVisibility) > 0 { | ||||
| 			entryConfig["listing_visibility"] = entry.Config.ListingVisibility | ||||
| 		} | ||||
| 		if rawVal, ok := entry.synthesizedConfigCache.Load("passthrough_request_headers"); ok { | ||||
| 			entryConfig["passthrough_request_headers"] = rawVal.([]string) | ||||
| 		} | ||||
|  | ||||
| 		info["config"] = entryConfig | ||||
| 		resp.Data[entry.Path] = info | ||||
| 	} | ||||
| @@ -2320,6 +2370,9 @@ func (b *SystemBackend) handleEnableAuth(ctx context.Context, req *logical.Reque | ||||
| 	if len(apiConfig.AuditNonHMACResponseKeys) > 0 { | ||||
| 		config.AuditNonHMACResponseKeys = apiConfig.AuditNonHMACResponseKeys | ||||
| 	} | ||||
| 	if len(apiConfig.PassthroughRequestHeaders) > 0 { | ||||
| 		config.PassthroughRequestHeaders = apiConfig.PassthroughRequestHeaders | ||||
| 	} | ||||
|  | ||||
| 	// Create the mount entry | ||||
| 	me := &MountEntry{ | ||||
| @@ -3820,4 +3873,7 @@ This path responds to the following HTTP methods. | ||||
| 	"listing_visibility": { | ||||
| 		"Determines the visibility of the mount in the UI-specific listing endpoint.", | ||||
| 	}, | ||||
| 	"passthrough_request_headers": { | ||||
| 		"A list of headers to whitelist and pass from the request to the backend.", | ||||
| 	}, | ||||
| } | ||||
|   | ||||
| @@ -184,7 +184,10 @@ type MountEntry struct { | ||||
| 	SealWrap         bool              `json:"seal_wrap"`          // Whether to wrap CSPs | ||||
| 	Tainted          bool              `json:"tainted,omitempty"`  // Set as a Write-Ahead flag for unmount/remount | ||||
|  | ||||
| 	// synthesizedConfigCache is used to cache configuration values | ||||
| 	// synthesizedConfigCache is used to cache configuration values. These | ||||
| 	// particular values are cached since we want to get them at a point-in-time | ||||
| 	// without separately managing their locks individually. See SyncCache() for | ||||
| 	// the specific values that are being cached. | ||||
| 	synthesizedConfigCache sync.Map | ||||
| } | ||||
|  | ||||
| @@ -197,6 +200,7 @@ type MountConfig struct { | ||||
| 	AuditNonHMACRequestKeys   []string             `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"` | ||||
| 	AuditNonHMACResponseKeys  []string             `json:"audit_non_hmac_response_keys,omitempty" structs:"audit_non_hmac_response_keys" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         ListingVisiblityType `json:"listing_visibility,omitempty" structs:"listing_visibility" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string             `json:"passthrough_request_headers,omitempty" structs:"passthrough_request_headers" mapstructure:"passthrough_request_headers"` | ||||
| } | ||||
|  | ||||
| // APIMountConfig is an embedded struct of api.MountConfigInput | ||||
| @@ -208,6 +212,7 @@ type APIMountConfig struct { | ||||
| 	AuditNonHMACRequestKeys   []string             `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"` | ||||
| 	AuditNonHMACResponseKeys  []string             `json:"audit_non_hmac_response_keys,omitempty" structs:"audit_non_hmac_response_keys" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         ListingVisiblityType `json:"listing_visibility,omitempty" structs:"listing_visibility" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string             `json:"passthrough_request_headers,omitempty" structs:"passthrough_request_headers" mapstructure:"passthrough_request_headers"` | ||||
| } | ||||
|  | ||||
| // Clone returns a deep copy of the mount entry | ||||
| @@ -219,7 +224,9 @@ func (e *MountEntry) Clone() (*MountEntry, error) { | ||||
| 	return cp.(*MountEntry), nil | ||||
| } | ||||
|  | ||||
| // SyncCache syncs tunable configuration values to the cache | ||||
| // SyncCache syncs tunable configuration values to the cache. In the case of | ||||
| // cached values, they should be retrieved via synthesizedConfigCache.Load() | ||||
| // instead of accessing them directly through MountConfig. | ||||
| func (e *MountEntry) SyncCache() { | ||||
| 	if len(e.Config.AuditNonHMACRequestKeys) == 0 { | ||||
| 		e.synthesizedConfigCache.Delete("audit_non_hmac_request_keys") | ||||
| @@ -232,6 +239,12 @@ func (e *MountEntry) SyncCache() { | ||||
| 	} else { | ||||
| 		e.synthesizedConfigCache.Store("audit_non_hmac_response_keys", e.Config.AuditNonHMACResponseKeys) | ||||
| 	} | ||||
|  | ||||
| 	if len(e.Config.PassthroughRequestHeaders) == 0 { | ||||
| 		e.synthesizedConfigCache.Delete("passthrough_request_headers") | ||||
| 	} else { | ||||
| 		e.synthesizedConfigCache.Store("passthrough_request_headers", e.Config.PassthroughRequestHeaders) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Mount is used to mount a new backend to the mount table. | ||||
|   | ||||
| @@ -15,6 +15,12 @@ import ( | ||||
| 	"github.com/hashicorp/vault/logical" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	whitelistedHeaders = []string{ | ||||
| 		consts.VaultKVCLIClientHeader, | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| // Router is used to do prefix based routing of a request to a logical backend | ||||
| type Router struct { | ||||
| 	l                  sync.RWMutex | ||||
| @@ -473,16 +479,15 @@ func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenc | ||||
| 	originalClientTokenRemainingUses := req.ClientTokenRemainingUses | ||||
| 	req.ClientTokenRemainingUses = 0 | ||||
|  | ||||
| 	// Cache the headers and hide them from backends | ||||
| 	// Cache the headers | ||||
| 	headers := req.Headers | ||||
| 	req.Headers = nil | ||||
|  | ||||
| 	// Whitelist the X-Vault-Kv-Client header for use in the kv backend. | ||||
| 	if val, ok := headers[consts.VaultKVCLIClientHeader]; ok { | ||||
| 		req.Headers = map[string][]string{ | ||||
| 			consts.VaultKVCLIClientHeader: val, | ||||
| 		} | ||||
| 	// Filter and add passthrough headers to the backend | ||||
| 	var passthroughRequestHeaders []string | ||||
| 	if rawVal, ok := re.mountEntry.synthesizedConfigCache.Load("passthrough_request_headers"); ok { | ||||
| 		passthroughRequestHeaders = rawVal.([]string) | ||||
| 	} | ||||
| 	req.Headers = filteredPassthroughHeaders(headers, passthroughRequestHeaders) | ||||
|  | ||||
| 	// Cache the wrap info of the request | ||||
| 	var wrapInfo *logical.RequestWrapInfo | ||||
| @@ -624,3 +629,48 @@ func pathsToRadix(paths []string) *radix.Tree { | ||||
|  | ||||
| 	return tree | ||||
| } | ||||
|  | ||||
| // filteredPassthroughHeaders returns a headers map[string][]string that | ||||
| // contains the filtered values contained in passthroughHeaders, as well as the | ||||
| // values in whitelistedHeaders. Filtering of passthroughHeaders from the | ||||
| // origHeaders is done is a case-insensitive manner. | ||||
| func filteredPassthroughHeaders(origHeaders map[string][]string, passthroughHeaders []string) map[string][]string { | ||||
| 	retHeaders := make(map[string][]string) | ||||
|  | ||||
| 	// Handle whitelisted values | ||||
| 	for _, header := range whitelistedHeaders { | ||||
| 		if val, ok := origHeaders[header]; ok { | ||||
| 			retHeaders[header] = val | ||||
| 		} else { | ||||
| 			// Try to check if a lowercased version of the header exists in the | ||||
| 			// originating request. The header key that gets used is the one from the | ||||
| 			// whitelist. | ||||
| 			if val, ok := origHeaders[strings.ToLower(header)]; ok { | ||||
| 				retHeaders[header] = val | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Short-circuit if there's nothing to filter | ||||
| 	if len(passthroughHeaders) == 0 { | ||||
| 		return retHeaders | ||||
| 	} | ||||
|  | ||||
| 	// Create a map that uses lowercased header values as the key and the original | ||||
| 	// header naming as the value for comparison down below. | ||||
| 	lowerHeadersRef := make(map[string]string, len(origHeaders)) | ||||
| 	for key := range origHeaders { | ||||
| 		lowerHeadersRef[strings.ToLower(key)] = key | ||||
| 	} | ||||
|  | ||||
| 	// Case-insensitive compare of passthrough headers against originating | ||||
| 	// headers. The returned headers will be the same casing as the originating | ||||
| 	// header name. | ||||
| 	for _, ph := range passthroughHeaders { | ||||
| 		if header, ok := lowerHeadersRef[strings.ToLower(ph)]; ok { | ||||
| 			retHeaders[header] = origHeaders[header] | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return retHeaders | ||||
| } | ||||
|   | ||||
| @@ -92,6 +92,12 @@ For example, enable the "foo" auth method will make it accessible at | ||||
|   - `audit_non_hmac_response_keys` `(array: [])` - Comma-separated list of keys | ||||
|      that will not be HMAC'd by audit devices in the response data object. | ||||
|  | ||||
|   - `listing_visibility` `(string: "")` - Speficies whether to show this mount | ||||
|      in the UI-specific listing endpoint. | ||||
|  | ||||
|   - `passthrough_request_headers` `(array: [])` - Comma-separated list of headers | ||||
|      to whitelist and pass from the request to the backend. | ||||
|  | ||||
|     The plugin_name can be provided in the config map or as a top-level option, | ||||
|     with the former taking precedence. | ||||
|  | ||||
| @@ -215,6 +221,12 @@ can be achieved without `sudo` via `sys/mounts/auth/[auth-path]/tune`._ | ||||
|   list of keys that will not be HMAC'd by audit devices in the response data | ||||
|   object. | ||||
|  | ||||
| - `listing_visibility` `(string: "")` - Speficies whether to show this mount | ||||
|     in the UI-specific listing endpoint. | ||||
|  | ||||
| - `passthrough_request_headers` `(array: [])` - Comma-separated list of headers | ||||
|     to whitelist and pass from the request to the backend. | ||||
|  | ||||
| ### Sample Payload | ||||
|  | ||||
| ```json | ||||
|   | ||||
| @@ -97,6 +97,12 @@ This endpoint enables a new secrets engine at the given path. | ||||
|   - `audit_non_hmac_response_keys` `(array: [])` - Comma-separated list of keys | ||||
|      that will not be HMAC'd by audit devices in the response data object. | ||||
|  | ||||
|   - `listing_visibility` `(string: "")` - Speficies whether to show this mount | ||||
|      in the UI-specific listing endpoint. | ||||
|  | ||||
|   - `passthrough_request_headers` `(array: [])` - Comma-separated list of headers | ||||
|      to whitelist and pass from the request to the backend. | ||||
|  | ||||
|     These control the default and maximum lease time-to-live, force | ||||
|     disabling backend caching, and option plugin name for plugin backends | ||||
|     respectively. The first three options override the global defaults if | ||||
| @@ -216,6 +222,12 @@ This endpoint tunes configuration parameters for a given mount point. | ||||
|   list of keys that will not be HMAC'd by audit devices in the response data | ||||
|   object. | ||||
|  | ||||
| - `listing_visibility` `(string: "")` - Speficies whether to show this mount | ||||
|     in the UI-specific listing endpoint. | ||||
|  | ||||
| - `passthrough_request_headers` `(array: [])` - Comma-separated list of headers | ||||
|     to whitelist and pass from the request to the backend. | ||||
|  | ||||
| ### Sample Payload | ||||
|  | ||||
| ```json | ||||
|   | ||||
		Reference in New Issue
	
	Block a user