Unauthenticated endpoint to list secret and auth mounts (#4134)
* Add audit hmac values to AuthConfigInput and AuthConfigOutput, fix docs * docs: Add ttl params to auth enable endpoint * Rewording of go string to simply string * Add audit hmac keys as CLI flags on auth/secrets enable * Fix copypasta mistake * WIP on auth-list endpoint * Rename variable to be singular, add CLI flag, show value in auth and secrets list * Add audit hmac keys to auth and secrets list * Only set config values if they exist * Fix http sys/auth tests * More auth plugin_name test fixes * Rename tag internal_ui_show_mount to _ui_show_mount * Add tests * Make endpoint unauthed * Rename field to listing_visibility * Add listing-visibility to cli tune commands * Use ListingVisiblityType * Fix type conversion * Do not actually change token's value on testHttpGet * Remove unused ListingVisibilityAuth, use const in pathInternalUIMountsRead
This commit is contained in:
parent
414097018a
commit
f86881c295
|
@ -96,6 +96,7 @@ type AuthConfigInput struct {
|
|||
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"`
|
||||
}
|
||||
|
||||
type AuthMount struct {
|
||||
|
@ -113,4 +114,5 @@ type AuthConfigOutput struct {
|
|||
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"`
|
||||
}
|
||||
|
|
|
@ -135,6 +135,7 @@ type MountConfigInput struct {
|
|||
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"`
|
||||
}
|
||||
|
||||
type MountOutput struct {
|
||||
|
@ -153,4 +154,5 @@ type MountConfigOutput struct {
|
|||
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"`
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ type AuthEnableCommand struct {
|
|||
flagMaxLeaseTTL time.Duration
|
||||
flagAuditNonHMACRequestKeys []string
|
||||
flagAuditNonHMACResponseKeys []string
|
||||
flagListingVisibility string
|
||||
flagPluginName string
|
||||
flagLocal bool
|
||||
flagSealWrap bool
|
||||
|
@ -113,6 +114,12 @@ func (c *AuthEnableCommand) Flags() *FlagSets {
|
|||
"devices in the response data object.",
|
||||
})
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: flagNameListingVisibility,
|
||||
Target: &c.flagListingVisibility,
|
||||
Usage: "Determines the visibility of the mount in the UI-specific listing endpoint.",
|
||||
})
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: "plugin-name",
|
||||
Target: &c.flagPluginName,
|
||||
|
@ -208,6 +215,10 @@ func (c *AuthEnableCommand) Run(args []string) int {
|
|||
if fl.Name == flagNameAuditNonHMACResponseKeys {
|
||||
authOpts.Config.AuditNonHMACResponseKeys = c.flagAuditNonHMACResponseKeys
|
||||
}
|
||||
|
||||
if fl.Name == flagNameListingVisibility {
|
||||
authOpts.Config.ListingVisibility = c.flagListingVisibility
|
||||
}
|
||||
})
|
||||
|
||||
if err := client.Sys().EnableAuthWithOptions(authPath, authOpts); err != nil {
|
||||
|
|
|
@ -21,6 +21,7 @@ type AuthTuneCommand struct {
|
|||
flagMaxLeaseTTL time.Duration
|
||||
flagAuditNonHMACRequestKeys []string
|
||||
flagAuditNonHMACResponseKeys []string
|
||||
flagListingVisibility string
|
||||
}
|
||||
|
||||
func (c *AuthTuneCommand) Synopsis() string {
|
||||
|
@ -85,6 +86,12 @@ func (c *AuthTuneCommand) Flags() *FlagSets {
|
|||
"devices in the response data object.",
|
||||
})
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: flagNameListingVisibility,
|
||||
Target: &c.flagListingVisibility,
|
||||
Usage: "Determines the visibility of the mount in the UI-specific listing endpoint.",
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
|
@ -134,6 +141,10 @@ func (c *AuthTuneCommand) Run(args []string) int {
|
|||
if fl.Name == flagNameAuditNonHMACResponseKeys {
|
||||
mountConfigInput.AuditNonHMACResponseKeys = c.flagAuditNonHMACResponseKeys
|
||||
}
|
||||
|
||||
if fl.Name == flagNameListingVisibility {
|
||||
mountConfigInput.ListingVisibility = c.flagListingVisibility
|
||||
}
|
||||
})
|
||||
|
||||
// Append /auth (since that's where auths live) and a trailing slash to
|
||||
|
|
|
@ -85,6 +85,9 @@ func TestAuthTuneCommand_Run(t *testing.T) {
|
|||
code := cmd.Run([]string{
|
||||
"-default-lease-ttl", "30m",
|
||||
"-max-lease-ttl", "1h",
|
||||
"-audit-non-hmac-request-keys", "foo,bar",
|
||||
"-audit-non-hmac-response-keys", "foo,bar",
|
||||
"-listing-visibility", "unauth",
|
||||
"my-auth/",
|
||||
})
|
||||
if exp := 0; code != exp {
|
||||
|
|
|
@ -76,6 +76,8 @@ const (
|
|||
flagNameAuditNonHMACRequestKeys = "audit-non-hmac-request-keys"
|
||||
// flagNameAuditNonHMACResponseKeys is the flag name used for auth/secrets enable
|
||||
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"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -23,6 +23,7 @@ type SecretsEnableCommand struct {
|
|||
flagMaxLeaseTTL time.Duration
|
||||
flagAuditNonHMACRequestKeys []string
|
||||
flagAuditNonHMACResponseKeys []string
|
||||
flagListingVisibility string
|
||||
flagForceNoCache bool
|
||||
flagPluginName string
|
||||
flagLocal bool
|
||||
|
@ -121,6 +122,12 @@ func (c *SecretsEnableCommand) Flags() *FlagSets {
|
|||
"devices in the response data object.",
|
||||
})
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: flagNameListingVisibility,
|
||||
Target: &c.flagListingVisibility,
|
||||
Usage: "Determines the visibility of the mount in the UI-specific listing endpoint.",
|
||||
})
|
||||
|
||||
f.BoolVar(&BoolVar{
|
||||
Name: "force-no-cache",
|
||||
Target: &c.flagForceNoCache,
|
||||
|
@ -228,6 +235,10 @@ func (c *SecretsEnableCommand) Run(args []string) int {
|
|||
if fl.Name == flagNameAuditNonHMACResponseKeys {
|
||||
mountInput.Config.AuditNonHMACResponseKeys = c.flagAuditNonHMACResponseKeys
|
||||
}
|
||||
|
||||
if fl.Name == flagNameListingVisibility {
|
||||
mountInput.Config.ListingVisibility = c.flagListingVisibility
|
||||
}
|
||||
})
|
||||
|
||||
if err := client.Sys().Mount(mountPath, mountInput); err != nil {
|
||||
|
|
|
@ -21,6 +21,7 @@ type SecretsTuneCommand struct {
|
|||
flagMaxLeaseTTL time.Duration
|
||||
flagAuditNonHMACRequestKeys []string
|
||||
flagAuditNonHMACResponseKeys []string
|
||||
flagListingVisibility string
|
||||
}
|
||||
|
||||
func (c *SecretsTuneCommand) Synopsis() string {
|
||||
|
@ -85,6 +86,12 @@ func (c *SecretsTuneCommand) Flags() *FlagSets {
|
|||
"devices in the response data object.",
|
||||
})
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: flagNameListingVisibility,
|
||||
Target: &c.flagListingVisibility,
|
||||
Usage: "Determines the visibility of the mount in the UI-specific listing endpoint.",
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
|
@ -137,6 +144,10 @@ func (c *SecretsTuneCommand) Run(args []string) int {
|
|||
if fl.Name == flagNameAuditNonHMACResponseKeys {
|
||||
mountConfigInput.AuditNonHMACResponseKeys = c.flagAuditNonHMACResponseKeys
|
||||
}
|
||||
|
||||
if fl.Name == flagNameListingVisibility {
|
||||
mountConfigInput.ListingVisibility = c.flagListingVisibility
|
||||
}
|
||||
})
|
||||
|
||||
if err := client.Sys().TuneMount(mountPath, mountConfigInput); err != nil {
|
||||
|
|
|
@ -85,6 +85,9 @@ func TestSecretsTuneCommand_Run(t *testing.T) {
|
|||
code := cmd.Run([]string{
|
||||
"-default-lease-ttl", "30m",
|
||||
"-max-lease-ttl", "1h",
|
||||
"-audit-non-hmac-request-keys", "foo,bar",
|
||||
"-audit-non-hmac-response-keys", "foo,bar",
|
||||
"-listing-visibility", "unauth",
|
||||
"mount_tune_integration/",
|
||||
})
|
||||
if exp := 0; code != exp {
|
||||
|
|
|
@ -16,7 +16,11 @@ import (
|
|||
)
|
||||
|
||||
func testHttpGet(t *testing.T, token string, addr string) *http.Response {
|
||||
t.Logf("Token is %s", token)
|
||||
loggedToken := token
|
||||
if len(token) == 0 {
|
||||
loggedToken = "<empty>"
|
||||
}
|
||||
t.Logf("Token is %s", loggedToken)
|
||||
return testHttpData(t, "GET", token, addr, nil, false)
|
||||
}
|
||||
|
||||
|
|
|
@ -218,3 +218,159 @@ func TestSysDisableAuth(t *testing.T) {
|
|||
t.Fatalf("bad: expected:%#v\nactual:%#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysTuneAuth_nonHMACKeys(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/auth/token/tune", map[string]interface{}{
|
||||
"audit_non_hmac_request_keys": "foo",
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
// Mount-tune the audit_non_hmac_response_keys
|
||||
resp = testHttpPost(t, token, addr+"/v1/sys/auth/token/tune", map[string]interface{}{
|
||||
"audit_non_hmac_response_keys": "bar",
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
// Check results
|
||||
resp = testHttpGet(t, token, addr+"/v1/sys/auth/token/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,
|
||||
"audit_non_hmac_request_keys": []interface{}{"foo"},
|
||||
"audit_non_hmac_response_keys": []interface{}{"bar"},
|
||||
},
|
||||
"default_lease_ttl": json.Number("2764800"),
|
||||
"max_lease_ttl": json.Number("2764800"),
|
||||
"force_no_cache": false,
|
||||
"audit_non_hmac_request_keys": []interface{}{"foo"},
|
||||
"audit_non_hmac_response_keys": []interface{}{"bar"},
|
||||
}
|
||||
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 those mount tune values
|
||||
resp = testHttpPost(t, token, addr+"/v1/sys/auth/token/tune", map[string]interface{}{
|
||||
"audit_non_hmac_request_keys": "",
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
resp = testHttpPost(t, token, addr+"/v1/sys/auth/token/tune", map[string]interface{}{
|
||||
"audit_non_hmac_response_keys": "",
|
||||
})
|
||||
|
||||
// Check results
|
||||
resp = testHttpGet(t, token, addr+"/v1/sys/auth/token/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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysTuneAuth_showUIMount(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
// Get original tune values, ensure that listing_visibility is not set
|
||||
resp := testHttpGet(t, token, addr+"/v1/sys/auth/token/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)
|
||||
}
|
||||
|
||||
// Mount-tune the listing_visibility
|
||||
resp = testHttpPost(t, token, addr+"/v1/sys/auth/token/tune", map[string]interface{}{
|
||||
"listing_visibility": "unauth",
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
// Check results
|
||||
resp = testHttpGet(t, token, addr+"/v1/sys/auth/token/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,
|
||||
"listing_visibility": "unauth",
|
||||
},
|
||||
"default_lease_ttl": json.Number("2764800"),
|
||||
"max_lease_ttl": json.Number("2764800"),
|
||||
"force_no_cache": false,
|
||||
"listing_visibility": "unauth",
|
||||
}
|
||||
testResponseBody(t, resp, &actual)
|
||||
expected["request_id"] = actual["request_id"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
func TestSysInternal_UIMounts(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
// Get original tune values, ensure that listing_visibility is not set
|
||||
resp := testHttpGet(t, "", addr+"/v1/sys/internal/ui/mounts")
|
||||
testResponseStatus(t, resp, 200)
|
||||
|
||||
actual := map[string]interface{}{}
|
||||
expected := map[string]interface{}{
|
||||
"wrap_info": nil,
|
||||
"warnings": nil,
|
||||
"auth": nil,
|
||||
"lease_id": "",
|
||||
"renewable": false,
|
||||
"lease_duration": json.Number("0"),
|
||||
"data": map[string]interface{}{
|
||||
"auth": map[string]interface{}{},
|
||||
"secret": map[string]interface{}{},
|
||||
},
|
||||
}
|
||||
testResponseBody(t, resp, &actual)
|
||||
expected["request_id"] = actual["request_id"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, actual)
|
||||
}
|
||||
|
||||
// Mount-tune the listing_visibility
|
||||
resp = testHttpPost(t, token, addr+"/v1/sys/mounts/secret/tune", map[string]interface{}{
|
||||
"listing_visibility": "unauth",
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
resp = testHttpPost(t, token, addr+"/v1/sys/auth/token/tune", map[string]interface{}{
|
||||
"listing_visibility": "unauth",
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
// Check results
|
||||
resp = testHttpGet(t, "", addr+"/v1/sys/internal/ui/mounts")
|
||||
testResponseStatus(t, resp, 200)
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"wrap_info": nil,
|
||||
"warnings": nil,
|
||||
"auth": nil,
|
||||
"lease_id": "",
|
||||
"renewable": false,
|
||||
"lease_duration": json.Number("0"),
|
||||
"data": map[string]interface{}{
|
||||
"secret": map[string]interface{}{
|
||||
"secret/": map[string]interface{}{
|
||||
"type": "kv",
|
||||
"description": "key/value secret storage",
|
||||
},
|
||||
},
|
||||
"auth": map[string]interface{}{
|
||||
"token/": map[string]interface{}{
|
||||
"type": "token",
|
||||
"description": "token based credentials",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
testResponseBody(t, resp, &actual)
|
||||
expected["request_id"] = actual["request_id"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, actual)
|
||||
}
|
||||
}
|
|
@ -1159,3 +1159,72 @@ func TestSysTuneMount_nonHMACKeys(t *testing.T) {
|
|||
t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysTuneMount_showUIMount(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
// Get original tune values, ensure that listing_visibility is not set
|
||||
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)
|
||||
}
|
||||
|
||||
// Mount-tune the listing_visibility
|
||||
resp = testHttpPost(t, token, addr+"/v1/sys/mounts/secret/tune", map[string]interface{}{
|
||||
"listing_visibility": "unauth",
|
||||
})
|
||||
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,
|
||||
"listing_visibility": "unauth",
|
||||
},
|
||||
"default_lease_ttl": json.Number("2764800"),
|
||||
"max_lease_ttl": json.Number("2764800"),
|
||||
"force_no_cache": false,
|
||||
"listing_visibility": "unauth",
|
||||
}
|
||||
testResponseBody(t, resp, &actual)
|
||||
expected["request_id"] = actual["request_id"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ func NewSystemBackend(core *Core) *SystemBackend {
|
|||
"wrapping/lookup",
|
||||
"wrapping/pubkey",
|
||||
"replication/status",
|
||||
"internal/ui/mounts",
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -265,6 +266,10 @@ func NewSystemBackend(core *Core) *SystemBackend {
|
|||
Type: framework.TypeCommaStringSlice,
|
||||
Description: strings.TrimSpace(sysHelp["tune_audit_non_hmac_response_keys"][0]),
|
||||
},
|
||||
"listing_visibility": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: strings.TrimSpace(sysHelp["listing_visibility"][0]),
|
||||
},
|
||||
},
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: b.handleAuthTuneRead,
|
||||
|
@ -302,6 +307,10 @@ func NewSystemBackend(core *Core) *SystemBackend {
|
|||
Type: framework.TypeCommaStringSlice,
|
||||
Description: strings.TrimSpace(sysHelp["tune_audit_non_hmac_response_keys"][0]),
|
||||
},
|
||||
"listing_visibility": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: strings.TrimSpace(sysHelp["listing_visibility"][0]),
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
|
@ -993,6 +1002,14 @@ func NewSystemBackend(core *Core) *SystemBackend {
|
|||
HelpSynopsis: strings.TrimSpace(sysHelp["random"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysHelp["random"][1]),
|
||||
},
|
||||
&framework.Path{
|
||||
Pattern: "internal/ui/mounts",
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: b.pathInternalUIMountsRead,
|
||||
},
|
||||
HelpSynopsis: strings.TrimSpace(sysHelp["internal-ui-mounts"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysHelp["internal-ui-mounts"][1]),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1458,6 +1475,11 @@ func (b *SystemBackend) handleMountTable(ctx context.Context, req *logical.Reque
|
|||
if rawVal, ok := entry.synthesizedConfigCache.Load("audit_non_hmac_response_keys"); ok {
|
||||
entryConfig["audit_non_hmac_response_keys"] = rawVal.([]string)
|
||||
}
|
||||
// Even though empty value is valid for ListingVisibility, we can ignore
|
||||
// this case during mount since there's nothing to unset/hide.
|
||||
if len(entry.Config.ListingVisibility) > 0 {
|
||||
entryConfig["listing_visibility"] = entry.Config.ListingVisibility
|
||||
}
|
||||
info["config"] = entryConfig
|
||||
resp.Data[entry.Path] = info
|
||||
}
|
||||
|
@ -1476,13 +1498,13 @@ func (b *SystemBackend) handleMount(ctx context.Context, req *logical.Request, d
|
|||
|
||||
// Get all the options
|
||||
path := data.Get("path").(string)
|
||||
path = sanitizeMountPath(path)
|
||||
|
||||
logicalType := data.Get("type").(string)
|
||||
description := data.Get("description").(string)
|
||||
pluginName := data.Get("plugin_name").(string)
|
||||
sealWrap := data.Get("seal_wrap").(bool)
|
||||
|
||||
path = sanitizeMountPath(path)
|
||||
|
||||
var config MountConfig
|
||||
var apiConfig APIMountConfig
|
||||
|
||||
|
@ -1560,10 +1582,14 @@ func (b *SystemBackend) handleMount(ctx context.Context, req *logical.Request, d
|
|||
config.ForceNoCache = true
|
||||
}
|
||||
|
||||
if err := checkListingVisibility(apiConfig.ListingVisibility); err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("invalid listing_visibility %s", apiConfig.ListingVisibility)), nil
|
||||
}
|
||||
config.ListingVisibility = apiConfig.ListingVisibility
|
||||
|
||||
if len(apiConfig.AuditNonHMACRequestKeys) > 0 {
|
||||
config.AuditNonHMACRequestKeys = apiConfig.AuditNonHMACRequestKeys
|
||||
}
|
||||
|
||||
if len(apiConfig.AuditNonHMACResponseKeys) > 0 {
|
||||
config.AuditNonHMACResponseKeys = apiConfig.AuditNonHMACResponseKeys
|
||||
}
|
||||
|
@ -1732,6 +1758,10 @@ func (b *SystemBackend) handleTuneReadCommon(path string) (*logical.Response, er
|
|||
resp.Data["audit_non_hmac_response_keys"] = rawVal.([]string)
|
||||
}
|
||||
|
||||
if len(mountEntry.Config.ListingVisibility) > 0 {
|
||||
resp.Data["listing_visibility"] = mountEntry.Config.ListingVisibility
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
|
@ -1917,6 +1947,35 @@ func (b *SystemBackend) handleTuneWriteCommon(ctx context.Context, path string,
|
|||
}
|
||||
}
|
||||
|
||||
if rawVal, ok := data.GetOk("listing_visibility"); ok {
|
||||
lvString := rawVal.(string)
|
||||
listingVisibility := ListingVisiblityType(lvString)
|
||||
|
||||
if err := checkListingVisibility(listingVisibility); err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("invalid listing_visibility %s", listingVisibility)), nil
|
||||
}
|
||||
|
||||
oldVal := mountEntry.Config.ListingVisibility
|
||||
mountEntry.Config.ListingVisibility = listingVisibility
|
||||
|
||||
// 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.ListingVisibility = oldVal
|
||||
return handleError(err)
|
||||
}
|
||||
|
||||
if b.Core.logger.IsInfo() {
|
||||
b.Core.logger.Info("core: mount tuning of listing_visibility successful", "path", path)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -2075,6 +2134,11 @@ func (b *SystemBackend) handleAuthTable(ctx context.Context, req *logical.Reques
|
|||
if rawVal, ok := entry.synthesizedConfigCache.Load("audit_non_hmac_response_keys"); ok {
|
||||
entryConfig["audit_non_hmac_response_keys"] = rawVal.([]string)
|
||||
}
|
||||
// Even though empty value is valid for ListingVisibility, we can ignore
|
||||
// this case during mount since there's nothing to unset/hide.
|
||||
if len(entry.Config.ListingVisibility) > 0 {
|
||||
entryConfig["listing_visibility"] = entry.Config.ListingVisibility
|
||||
}
|
||||
info["config"] = entryConfig
|
||||
resp.Data[entry.Path] = info
|
||||
}
|
||||
|
@ -2091,6 +2155,7 @@ func (b *SystemBackend) handleEnableAuth(ctx context.Context, req *logical.Reque
|
|||
|
||||
// Get all the options
|
||||
path := data.Get("path").(string)
|
||||
path = sanitizeMountPath(path)
|
||||
logicalType := data.Get("type").(string)
|
||||
description := data.Get("description").(string)
|
||||
pluginName := data.Get("plugin_name").(string)
|
||||
|
@ -2147,9 +2212,15 @@ func (b *SystemBackend) handleEnableAuth(ctx context.Context, req *logical.Reque
|
|||
logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
// Only set plugin name if mount is of type plugin, with apiConfig.PluginName
|
||||
// option taking precedence.
|
||||
if logicalType == "plugin" {
|
||||
switch logicalType {
|
||||
case "":
|
||||
return logical.ErrorResponse(
|
||||
"backend type must be specified as a string"),
|
||||
logical.ErrInvalidRequest
|
||||
|
||||
case "plugin":
|
||||
// Only set plugin name if mount is of type plugin, with apiConfig.PluginName
|
||||
// option taking precedence.
|
||||
switch {
|
||||
case apiConfig.PluginName != "":
|
||||
config.PluginName = apiConfig.PluginName
|
||||
|
@ -2162,18 +2233,14 @@ func (b *SystemBackend) handleEnableAuth(ctx context.Context, req *logical.Reque
|
|||
}
|
||||
}
|
||||
|
||||
if logicalType == "" {
|
||||
return logical.ErrorResponse(
|
||||
"backend type must be specified as a string"),
|
||||
logical.ErrInvalidRequest
|
||||
if err := checkListingVisibility(apiConfig.ListingVisibility); err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("invalid listing_visibility %s", apiConfig.ListingVisibility)), nil
|
||||
}
|
||||
|
||||
path = sanitizeMountPath(path)
|
||||
config.ListingVisibility = apiConfig.ListingVisibility
|
||||
|
||||
if len(apiConfig.AuditNonHMACRequestKeys) > 0 {
|
||||
config.AuditNonHMACRequestKeys = apiConfig.AuditNonHMACRequestKeys
|
||||
}
|
||||
|
||||
if len(apiConfig.AuditNonHMACResponseKeys) > 0 {
|
||||
config.AuditNonHMACResponseKeys = apiConfig.AuditNonHMACResponseKeys
|
||||
}
|
||||
|
@ -3054,6 +3121,42 @@ func (b *SystemBackend) pathRandomWrite(ctx context.Context, req *logical.Reques
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
func (b *SystemBackend) pathInternalUIMountsRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
b.Core.mountsLock.RLock()
|
||||
defer b.Core.mountsLock.RUnlock()
|
||||
|
||||
resp := &logical.Response{
|
||||
Data: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
secretMounts := make(map[string]interface{})
|
||||
authMounts := make(map[string]interface{})
|
||||
resp.Data["secret"] = secretMounts
|
||||
resp.Data["auth"] = authMounts
|
||||
|
||||
for _, entry := range b.Core.mounts.Entries {
|
||||
if entry.Config.ListingVisibility == ListingVisibilityUnauth {
|
||||
info := map[string]interface{}{
|
||||
"type": entry.Type,
|
||||
"description": entry.Description,
|
||||
}
|
||||
secretMounts[entry.Path] = info
|
||||
}
|
||||
}
|
||||
|
||||
for _, entry := range b.Core.auth.Entries {
|
||||
if entry.Config.ListingVisibility == ListingVisibilityUnauth {
|
||||
info := map[string]interface{}{
|
||||
"type": entry.Type,
|
||||
"description": entry.Description,
|
||||
}
|
||||
authMounts[entry.Path] = info
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func sanitizeMountPath(path string) string {
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
|
@ -3066,6 +3169,17 @@ func sanitizeMountPath(path string) string {
|
|||
return path
|
||||
}
|
||||
|
||||
func checkListingVisibility(visibility ListingVisiblityType) error {
|
||||
switch visibility {
|
||||
case ListingVisibilityHidden:
|
||||
case ListingVisibilityUnauth:
|
||||
default:
|
||||
return fmt.Errorf("invalid listing visilibity type")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const sysHelpRoot = `
|
||||
The system backend is built-in to Vault and cannot be remounted or
|
||||
unmounted. It contains the paths that are used to configure Vault itself
|
||||
|
@ -3624,4 +3738,7 @@ This path responds to the following HTTP methods.
|
|||
"Generate random bytes",
|
||||
"This function can be used to generate high-entropy random bytes.",
|
||||
},
|
||||
"listing_visibility": {
|
||||
"Determines the visibility of the mount in the UI-specific listing endpoint.",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -2193,3 +2193,56 @@ func TestSystemBackend_ToolsRandom(t *testing.T) {
|
|||
req.Data["bytes"] = -1
|
||||
doRequest(req, true, "", 0)
|
||||
}
|
||||
|
||||
func TestSystemBackend_InternalUIMounts(t *testing.T) {
|
||||
b := testSystemBackend(t)
|
||||
|
||||
// Ensure no entries are in the endpoint as a starting point
|
||||
req := logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts")
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
exp := map[string]interface{}{
|
||||
"secret": map[string]interface{}{},
|
||||
"auth": map[string]interface{}{},
|
||||
}
|
||||
if !reflect.DeepEqual(resp.Data, exp) {
|
||||
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
|
||||
}
|
||||
|
||||
// Mount-tune an auth mount
|
||||
req = logical.TestRequest(t, logical.UpdateOperation, "auth/token/tune")
|
||||
req.Data["listing_visibility"] = "unauth"
|
||||
b.HandleRequest(context.Background(), req)
|
||||
|
||||
// Mount-tune a secret mount
|
||||
req = logical.TestRequest(t, logical.UpdateOperation, "mounts/secret/tune")
|
||||
req.Data["listing_visibility"] = "unauth"
|
||||
b.HandleRequest(context.Background(), req)
|
||||
|
||||
req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts")
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
exp = map[string]interface{}{
|
||||
"secret": map[string]interface{}{
|
||||
"secret/": map[string]interface{}{
|
||||
"type": "kv",
|
||||
"description": "key/value secret storage",
|
||||
},
|
||||
},
|
||||
"auth": map[string]interface{}{
|
||||
"token/": map[string]interface{}{
|
||||
"type": "token",
|
||||
"description": "token based credentials",
|
||||
},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(resp.Data, exp) {
|
||||
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,16 @@ const (
|
|||
mountTableType = "mounts"
|
||||
)
|
||||
|
||||
// ListingVisiblityType represents the types for listing visilibity
|
||||
type ListingVisiblityType string
|
||||
|
||||
const (
|
||||
// ListingVisibilityHidden is the hidden type for listing visibility
|
||||
ListingVisibilityHidden ListingVisiblityType = ""
|
||||
// ListingVisibilityUnauth is the unauth type for listing visibility
|
||||
ListingVisibilityUnauth ListingVisiblityType = "unauth"
|
||||
)
|
||||
|
||||
var (
|
||||
// loadMountsFailed if loadMounts encounters an error
|
||||
errLoadMountsFailed = errors.New("failed to setup mount table")
|
||||
|
@ -179,22 +189,24 @@ type MountEntry struct {
|
|||
|
||||
// MountConfig is used to hold settable options
|
||||
type MountConfig struct {
|
||||
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
|
||||
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"`
|
||||
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"`
|
||||
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
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// APIMountConfig is an embedded struct of api.MountConfigInput
|
||||
type APIMountConfig 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"`
|
||||
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"`
|
||||
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 ListingVisiblityType `json:"listing_visibility,omitempty" structs:"listing_visibility" mapstructure:"listing_visibility"`
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of the mount entry
|
||||
|
|
Loading…
Reference in New Issue