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

Passthrough request headers #4172

Merged
merged 14 commits into from
Mar 21, 2018
26 changes: 14 additions & 12 deletions api/sys_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,13 @@ type EnableAuthOptions struct {
}

type AuthConfigInput struct {
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"`
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"`
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"`
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"`
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"`
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 {
Expand All @@ -111,10 +112,11 @@ type AuthMount struct {
}

type AuthConfigOutput struct {
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"`
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"`
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"`
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"`
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"`
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"`
}
34 changes: 18 additions & 16 deletions api/sys_mounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,15 @@ type MountInput struct {
}

type MountConfigInput struct {
Options map[string]string `json:"options" structs:"options" mapstructure:"options"`
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"`
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"`
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"`
Options map[string]string `json:"options" structs:"options" mapstructure:"options"`
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"`
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"`
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 {
Expand All @@ -151,12 +152,13 @@ type MountOutput struct {
}

type MountConfigOutput struct {
Options map[string]string `json:"options" structs:"options" mapstructure:"options"`
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"`
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"`
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"`
Options map[string]string `json:"options" structs:"options" mapstructure:"options"`
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"`
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"`
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"`
}
34 changes: 23 additions & 11 deletions command/auth_enable.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@ var _ cli.CommandAutocomplete = (*AuthEnableCommand)(nil)
type AuthEnableCommand struct {
*BaseCommand

flagDescription string
flagPath string
flagDefaultLeaseTTL time.Duration
flagMaxLeaseTTL time.Duration
flagAuditNonHMACRequestKeys []string
flagAuditNonHMACResponseKeys []string
flagListingVisibility string
flagPluginName string
flagOptions map[string]string
flagLocal bool
flagSealWrap bool
flagDescription string
flagPath string
flagDefaultLeaseTTL time.Duration
flagMaxLeaseTTL time.Duration
flagAuditNonHMACRequestKeys []string
flagAuditNonHMACResponseKeys []string
flagListingVisibility string
flagPassthroughRequestHeaders []string
flagPluginName string
flagOptions map[string]string
flagLocal bool
flagSealWrap bool
}

func (c *AuthEnableCommand) Synopsis() string {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,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 (
Expand Down
36 changes: 24 additions & 12 deletions command/secrets_enable.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ var _ cli.CommandAutocomplete = (*SecretsEnableCommand)(nil)
type SecretsEnableCommand struct {
*BaseCommand

flagDescription string
flagPath string
flagDefaultLeaseTTL time.Duration
flagMaxLeaseTTL time.Duration
flagAuditNonHMACRequestKeys []string
flagAuditNonHMACResponseKeys []string
flagListingVisibility string
flagForceNoCache bool
flagPluginName string
flagOptions map[string]string
flagLocal bool
flagSealWrap bool
flagDescription string
flagPath string
flagDefaultLeaseTTL time.Duration
flagMaxLeaseTTL time.Duration
flagAuditNonHMACRequestKeys []string
flagAuditNonHMACResponseKeys []string
flagListingVisibility string
flagPassthroughRequestHeaders []string
flagForceNoCache bool
flagPluginName string
flagOptions map[string]string
flagLocal bool
flagSealWrap bool
}

func (c *SecretsEnableCommand) Synopsis() string {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
78 changes: 77 additions & 1 deletion http/sys_mount_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
}
}
85 changes: 85 additions & 0 deletions vault/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
Loading