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
				
			| @@ -92,12 +92,13 @@ type EnableAuthOptions struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type AuthConfigInput struct { | type AuthConfigInput struct { | ||||||
| 	DefaultLeaseTTL          string   `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` | 	DefaultLeaseTTL           string   `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` | ||||||
| 	MaxLeaseTTL              string   `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` | 	MaxLeaseTTL               string   `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` | ||||||
| 	PluginName               string   `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` | 	PluginName                string   `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` | ||||||
| 	AuditNonHMACRequestKeys  []string `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"` | 	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"` | 	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"` | 	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 { | type AuthMount struct { | ||||||
| @@ -111,10 +112,11 @@ type AuthMount struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type AuthConfigOutput struct { | type AuthConfigOutput struct { | ||||||
| 	DefaultLeaseTTL          int      `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` | 	DefaultLeaseTTL           int      `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` | ||||||
| 	MaxLeaseTTL              int      `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` | 	MaxLeaseTTL               int      `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` | ||||||
| 	PluginName               string   `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` | 	PluginName                string   `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` | ||||||
| 	AuditNonHMACRequestKeys  []string `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"` | 	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"` | 	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"` | 	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"` | ||||||
| } | } | ||||||
|   | |||||||
| @@ -130,14 +130,15 @@ type MountInput struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type MountConfigInput struct { | type MountConfigInput struct { | ||||||
| 	Options                  map[string]string `json:"options" structs:"options" mapstructure:"options"` | 	Options                   map[string]string `json:"options" structs:"options" mapstructure:"options"` | ||||||
| 	DefaultLeaseTTL          string            `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` | 	DefaultLeaseTTL           string            `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` | ||||||
| 	MaxLeaseTTL              string            `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` | 	MaxLeaseTTL               string            `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` | ||||||
| 	ForceNoCache             bool              `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"` | 	ForceNoCache              bool              `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"` | ||||||
| 	PluginName               string            `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` | 	PluginName                string            `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` | ||||||
| 	AuditNonHMACRequestKeys  []string          `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"` | 	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"` | 	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"` | 	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 { | type MountOutput struct { | ||||||
| @@ -151,12 +152,13 @@ type MountOutput struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type MountConfigOutput struct { | type MountConfigOutput struct { | ||||||
| 	Options                  map[string]string `json:"options" structs:"options" mapstructure:"options"` | 	Options                   map[string]string `json:"options" structs:"options" mapstructure:"options"` | ||||||
| 	DefaultLeaseTTL          int               `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` | 	DefaultLeaseTTL           int               `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` | ||||||
| 	MaxLeaseTTL              int               `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` | 	MaxLeaseTTL               int               `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` | ||||||
| 	ForceNoCache             bool              `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"` | 	ForceNoCache              bool              `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"` | ||||||
| 	PluginName               string            `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` | 	PluginName                string            `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` | ||||||
| 	AuditNonHMACRequestKeys  []string          `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"` | 	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"` | 	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"` | 	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"` | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,17 +17,18 @@ var _ cli.CommandAutocomplete = (*AuthEnableCommand)(nil) | |||||||
| type AuthEnableCommand struct { | type AuthEnableCommand struct { | ||||||
| 	*BaseCommand | 	*BaseCommand | ||||||
|  |  | ||||||
| 	flagDescription              string | 	flagDescription               string | ||||||
| 	flagPath                     string | 	flagPath                      string | ||||||
| 	flagDefaultLeaseTTL          time.Duration | 	flagDefaultLeaseTTL           time.Duration | ||||||
| 	flagMaxLeaseTTL              time.Duration | 	flagMaxLeaseTTL               time.Duration | ||||||
| 	flagAuditNonHMACRequestKeys  []string | 	flagAuditNonHMACRequestKeys   []string | ||||||
| 	flagAuditNonHMACResponseKeys []string | 	flagAuditNonHMACResponseKeys  []string | ||||||
| 	flagListingVisibility        string | 	flagListingVisibility         string | ||||||
| 	flagPluginName               string | 	flagPassthroughRequestHeaders []string | ||||||
| 	flagOptions                  map[string]string | 	flagPluginName                string | ||||||
| 	flagLocal                    bool | 	flagOptions                   map[string]string | ||||||
| 	flagSealWrap                 bool | 	flagLocal                     bool | ||||||
|  | 	flagSealWrap                  bool | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *AuthEnableCommand) Synopsis() string { | func (c *AuthEnableCommand) Synopsis() string { | ||||||
| @@ -121,6 +122,13 @@ func (c *AuthEnableCommand) Flags() *FlagSets { | |||||||
| 		Usage:  "Determines the visibility of the mount in the UI-specific listing endpoint.", | 		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{ | 	f.StringVar(&StringVar{ | ||||||
| 		Name:       "plugin-name", | 		Name:       "plugin-name", | ||||||
| 		Target:     &c.flagPluginName, | 		Target:     &c.flagPluginName, | ||||||
| @@ -229,6 +237,10 @@ func (c *AuthEnableCommand) Run(args []string) int { | |||||||
| 		if fl.Name == flagNameListingVisibility { | 		if fl.Name == flagNameListingVisibility { | ||||||
| 			authOpts.Config.ListingVisibility = c.flagListingVisibility | 			authOpts.Config.ListingVisibility = c.flagListingVisibility | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if fl.Name == flagNamePassthroughRequestHeaders { | ||||||
|  | 			authOpts.Config.PassthroughRequestHeaders = c.flagPassthroughRequestHeaders | ||||||
|  | 		} | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	if err := client.Sys().EnableAuthWithOptions(authPath, authOpts); err != nil { | 	if err := client.Sys().EnableAuthWithOptions(authPath, authOpts); err != nil { | ||||||
|   | |||||||
| @@ -79,6 +79,8 @@ const ( | |||||||
| 	flagNameAuditNonHMACResponseKeys = "audit-non-hmac-response-keys" | 	flagNameAuditNonHMACResponseKeys = "audit-non-hmac-response-keys" | ||||||
| 	// flagListingVisibility is the flag to toggle whether to show the mount in the UI-specific listing endpoint | 	// flagListingVisibility is the flag to toggle whether to show the mount in the UI-specific listing endpoint | ||||||
| 	flagNameListingVisibility = "listing-visibility" | 	flagNameListingVisibility = "listing-visibility" | ||||||
|  | 	// flagNamePassthroughRequestHeaders is the flag name used to set passthrough request headers to the backend | ||||||
|  | 	flagNamePassthroughRequestHeaders = "passthrough-request-headers" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
|   | |||||||
| @@ -17,18 +17,19 @@ var _ cli.CommandAutocomplete = (*SecretsEnableCommand)(nil) | |||||||
| type SecretsEnableCommand struct { | type SecretsEnableCommand struct { | ||||||
| 	*BaseCommand | 	*BaseCommand | ||||||
|  |  | ||||||
| 	flagDescription              string | 	flagDescription               string | ||||||
| 	flagPath                     string | 	flagPath                      string | ||||||
| 	flagDefaultLeaseTTL          time.Duration | 	flagDefaultLeaseTTL           time.Duration | ||||||
| 	flagMaxLeaseTTL              time.Duration | 	flagMaxLeaseTTL               time.Duration | ||||||
| 	flagAuditNonHMACRequestKeys  []string | 	flagAuditNonHMACRequestKeys   []string | ||||||
| 	flagAuditNonHMACResponseKeys []string | 	flagAuditNonHMACResponseKeys  []string | ||||||
| 	flagListingVisibility        string | 	flagListingVisibility         string | ||||||
| 	flagForceNoCache             bool | 	flagPassthroughRequestHeaders []string | ||||||
| 	flagPluginName               string | 	flagForceNoCache              bool | ||||||
| 	flagOptions                  map[string]string | 	flagPluginName                string | ||||||
| 	flagLocal                    bool | 	flagOptions                   map[string]string | ||||||
| 	flagSealWrap                 bool | 	flagLocal                     bool | ||||||
|  | 	flagSealWrap                  bool | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *SecretsEnableCommand) Synopsis() string { | func (c *SecretsEnableCommand) Synopsis() string { | ||||||
| @@ -129,6 +130,13 @@ func (c *SecretsEnableCommand) Flags() *FlagSets { | |||||||
| 		Usage:  "Determines the visibility of the mount in the UI-specific listing endpoint.", | 		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{ | 	f.BoolVar(&BoolVar{ | ||||||
| 		Name:    "force-no-cache", | 		Name:    "force-no-cache", | ||||||
| 		Target:  &c.flagForceNoCache, | 		Target:  &c.flagForceNoCache, | ||||||
| @@ -249,6 +257,10 @@ func (c *SecretsEnableCommand) Run(args []string) int { | |||||||
| 		if fl.Name == flagNameListingVisibility { | 		if fl.Name == flagNameListingVisibility { | ||||||
| 			mountInput.Config.ListingVisibility = c.flagListingVisibility | 			mountInput.Config.ListingVisibility = c.flagListingVisibility | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if fl.Name == flagNamePassthroughRequestHeaders { | ||||||
|  | 			mountInput.Config.PassthroughRequestHeaders = c.flagPassthroughRequestHeaders | ||||||
|  | 		} | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	if err := client.Sys().Mount(mountPath, mountInput); err != nil { | 	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{}{ | 	resp = testHttpPost(t, token, addr+"/v1/sys/mounts/secret/tune", map[string]interface{}{ | ||||||
| 		"audit_non_hmac_response_keys": "", | 		"audit_non_hmac_response_keys": "", | ||||||
| 	}) | 	}) | ||||||
|  | 	testResponseStatus(t, resp, 204) | ||||||
|  |  | ||||||
| 	// Check results | 	// Check results | ||||||
| 	resp = testHttpGet(t, token, addr+"/v1/sys/mounts/secret/tune") | 	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) | 	core, _, token := vault.TestCoreUnsealed(t) | ||||||
| 	ln, addr := TestServer(t, core) | 	ln, addr := TestServer(t, core) | ||||||
| 	defer ln.Close() | 	defer ln.Close() | ||||||
| @@ -1390,3 +1391,78 @@ func TestSysTuneMount_showUIMount(t *testing.T) { | |||||||
| 		t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, actual) | 		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) | 		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, | 						Type:        framework.TypeString, | ||||||
| 						Description: strings.TrimSpace(sysHelp["listing_visibility"][0]), | 						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{ | 				Callbacks: map[logical.Operation]framework.OperationFunc{ | ||||||
| 					logical.ReadOperation:   b.handleAuthTuneRead, | 					logical.ReadOperation:   b.handleAuthTuneRead, | ||||||
| @@ -320,6 +324,10 @@ func NewSystemBackend(core *Core) *SystemBackend { | |||||||
| 						Type:        framework.TypeString, | 						Type:        framework.TypeString, | ||||||
| 						Description: strings.TrimSpace(sysHelp["listing_visibility"][0]), | 						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{ | 				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 { | 		if len(entry.Config.ListingVisibility) > 0 { | ||||||
| 			entryConfig["listing_visibility"] = entry.Config.ListingVisibility | 			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 | 		info["config"] = entryConfig | ||||||
| 		resp.Data[entry.Path] = info | 		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 { | 	if len(apiConfig.AuditNonHMACResponseKeys) > 0 { | ||||||
| 		config.AuditNonHMACResponseKeys = apiConfig.AuditNonHMACResponseKeys | 		config.AuditNonHMACResponseKeys = apiConfig.AuditNonHMACResponseKeys | ||||||
| 	} | 	} | ||||||
|  | 	if len(apiConfig.PassthroughRequestHeaders) > 0 { | ||||||
|  | 		config.PassthroughRequestHeaders = apiConfig.PassthroughRequestHeaders | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Create the mount entry | 	// Create the mount entry | ||||||
| 	me := &MountEntry{ | 	me := &MountEntry{ | ||||||
| @@ -1782,6 +1797,10 @@ func (b *SystemBackend) handleTuneReadCommon(path string) (*logical.Response, er | |||||||
| 		resp.Data["listing_visibility"] = mountEntry.Config.ListingVisibility | 		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 { | 	if len(mountEntry.Options) > 0 { | ||||||
| 		resp.Data["options"] = mountEntry.Options | 		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 | 	var resp *logical.Response | ||||||
| 	if optionsRaw, ok := data.GetOk("options"); ok { | 	if optionsRaw, ok := data.GetOk("options"); ok { | ||||||
| 		b.Core.logger.Info("core: mount tuning of options", "path", path) | 		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") | 		delete(mountEntry.Options, "upgrade") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return resp, nil | 	return resp, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -2214,6 +2260,10 @@ func (b *SystemBackend) handleAuthTable(ctx context.Context, req *logical.Reques | |||||||
| 		if len(entry.Config.ListingVisibility) > 0 { | 		if len(entry.Config.ListingVisibility) > 0 { | ||||||
| 			entryConfig["listing_visibility"] = entry.Config.ListingVisibility | 			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 | 		info["config"] = entryConfig | ||||||
| 		resp.Data[entry.Path] = info | 		resp.Data[entry.Path] = info | ||||||
| 	} | 	} | ||||||
| @@ -2320,6 +2370,9 @@ func (b *SystemBackend) handleEnableAuth(ctx context.Context, req *logical.Reque | |||||||
| 	if len(apiConfig.AuditNonHMACResponseKeys) > 0 { | 	if len(apiConfig.AuditNonHMACResponseKeys) > 0 { | ||||||
| 		config.AuditNonHMACResponseKeys = apiConfig.AuditNonHMACResponseKeys | 		config.AuditNonHMACResponseKeys = apiConfig.AuditNonHMACResponseKeys | ||||||
| 	} | 	} | ||||||
|  | 	if len(apiConfig.PassthroughRequestHeaders) > 0 { | ||||||
|  | 		config.PassthroughRequestHeaders = apiConfig.PassthroughRequestHeaders | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Create the mount entry | 	// Create the mount entry | ||||||
| 	me := &MountEntry{ | 	me := &MountEntry{ | ||||||
| @@ -3820,4 +3873,7 @@ This path responds to the following HTTP methods. | |||||||
| 	"listing_visibility": { | 	"listing_visibility": { | ||||||
| 		"Determines the visibility of the mount in the UI-specific listing endpoint.", | 		"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,30 +184,35 @@ type MountEntry struct { | |||||||
| 	SealWrap         bool              `json:"seal_wrap"`          // Whether to wrap CSPs | 	SealWrap         bool              `json:"seal_wrap"`          // Whether to wrap CSPs | ||||||
| 	Tainted          bool              `json:"tainted,omitempty"`  // Set as a Write-Ahead flag for unmount/remount | 	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 | 	synthesizedConfigCache sync.Map | ||||||
| } | } | ||||||
|  |  | ||||||
| // MountConfig is used to hold settable options | // MountConfig is used to hold settable options | ||||||
| type MountConfig struct { | type MountConfig struct { | ||||||
| 	DefaultLeaseTTL          time.Duration        `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` // Override for global default | 	DefaultLeaseTTL           time.Duration        `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` // Override for global default | ||||||
| 	MaxLeaseTTL              time.Duration        `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`             // Override for global default | 	MaxLeaseTTL               time.Duration        `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`             // Override for global default | ||||||
| 	ForceNoCache             bool                 `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"`          // Override for global default | 	ForceNoCache              bool                 `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"`          // Override for global default | ||||||
| 	PluginName               string               `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` | 	PluginName                string               `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` | ||||||
| 	AuditNonHMACRequestKeys  []string             `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"` | 	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"` | 	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"` | 	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 | // APIMountConfig is an embedded struct of api.MountConfigInput | ||||||
| type APIMountConfig struct { | type APIMountConfig struct { | ||||||
| 	DefaultLeaseTTL          string               `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` | 	DefaultLeaseTTL           string               `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` | ||||||
| 	MaxLeaseTTL              string               `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` | 	MaxLeaseTTL               string               `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` | ||||||
| 	ForceNoCache             bool                 `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"` | 	ForceNoCache              bool                 `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"` | ||||||
| 	PluginName               string               `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` | 	PluginName                string               `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` | ||||||
| 	AuditNonHMACRequestKeys  []string             `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"` | 	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"` | 	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"` | 	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 | // Clone returns a deep copy of the mount entry | ||||||
| @@ -219,7 +224,9 @@ func (e *MountEntry) Clone() (*MountEntry, error) { | |||||||
| 	return cp.(*MountEntry), nil | 	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() { | func (e *MountEntry) SyncCache() { | ||||||
| 	if len(e.Config.AuditNonHMACRequestKeys) == 0 { | 	if len(e.Config.AuditNonHMACRequestKeys) == 0 { | ||||||
| 		e.synthesizedConfigCache.Delete("audit_non_hmac_request_keys") | 		e.synthesizedConfigCache.Delete("audit_non_hmac_request_keys") | ||||||
| @@ -232,6 +239,12 @@ func (e *MountEntry) SyncCache() { | |||||||
| 	} else { | 	} else { | ||||||
| 		e.synthesizedConfigCache.Store("audit_non_hmac_response_keys", e.Config.AuditNonHMACResponseKeys) | 		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. | // Mount is used to mount a new backend to the mount table. | ||||||
|   | |||||||
| @@ -15,6 +15,12 @@ import ( | |||||||
| 	"github.com/hashicorp/vault/logical" | 	"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 | // Router is used to do prefix based routing of a request to a logical backend | ||||||
| type Router struct { | type Router struct { | ||||||
| 	l                  sync.RWMutex | 	l                  sync.RWMutex | ||||||
| @@ -473,16 +479,15 @@ func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenc | |||||||
| 	originalClientTokenRemainingUses := req.ClientTokenRemainingUses | 	originalClientTokenRemainingUses := req.ClientTokenRemainingUses | ||||||
| 	req.ClientTokenRemainingUses = 0 | 	req.ClientTokenRemainingUses = 0 | ||||||
|  |  | ||||||
| 	// Cache the headers and hide them from backends | 	// Cache the headers | ||||||
| 	headers := req.Headers | 	headers := req.Headers | ||||||
| 	req.Headers = nil |  | ||||||
|  |  | ||||||
| 	// Whitelist the X-Vault-Kv-Client header for use in the kv backend. | 	// Filter and add passthrough headers to the backend | ||||||
| 	if val, ok := headers[consts.VaultKVCLIClientHeader]; ok { | 	var passthroughRequestHeaders []string | ||||||
| 		req.Headers = map[string][]string{ | 	if rawVal, ok := re.mountEntry.synthesizedConfigCache.Load("passthrough_request_headers"); ok { | ||||||
| 			consts.VaultKVCLIClientHeader: val, | 		passthroughRequestHeaders = rawVal.([]string) | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  | 	req.Headers = filteredPassthroughHeaders(headers, passthroughRequestHeaders) | ||||||
|  |  | ||||||
| 	// Cache the wrap info of the request | 	// Cache the wrap info of the request | ||||||
| 	var wrapInfo *logical.RequestWrapInfo | 	var wrapInfo *logical.RequestWrapInfo | ||||||
| @@ -624,3 +629,48 @@ func pathsToRadix(paths []string) *radix.Tree { | |||||||
|  |  | ||||||
| 	return 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 |   - `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. |      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, |     The plugin_name can be provided in the config map or as a top-level option, | ||||||
|     with the former taking precedence. |     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 |   list of keys that will not be HMAC'd by audit devices in the response data | ||||||
|   object. |   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 | ### Sample Payload | ||||||
|  |  | ||||||
| ```json | ```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 |   - `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. |      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 |     These control the default and maximum lease time-to-live, force | ||||||
|     disabling backend caching, and option plugin name for plugin backends |     disabling backend caching, and option plugin name for plugin backends | ||||||
|     respectively. The first three options override the global defaults if |     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 |   list of keys that will not be HMAC'd by audit devices in the response data | ||||||
|   object. |   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 | ### Sample Payload | ||||||
|  |  | ||||||
| ```json | ```json | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user