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:
Calvin Leung Huang 2018-03-19 23:16:33 -04:00 committed by GitHub
parent 414097018a
commit f86881c295
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 576 additions and 26 deletions

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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 (

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)
}
}

83
http/sys_internal_test.go Normal file
View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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.",
},
}

View File

@ -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)
}
}

View File

@ -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