Return role info for each role on pathRoleList (#3532)

* Return role info for each role on pathRoleList

* Change roles -> key_info, only return key_type

* Do not initialize result map in parseRole, refactor ListResponseWithInfo

* Add role list test
This commit is contained in:
Calvin Leung Huang 2017-11-03 17:12:03 -04:00 committed by GitHub
parent 7672b1d168
commit 512b254820
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 174 additions and 63 deletions

View file

@ -271,6 +271,31 @@ func TestSSHBackend_Lookup(t *testing.T) {
}) })
} }
func TestSSHBackend_RoleList(t *testing.T) {
testOTPRoleData := map[string]interface{}{
"key_type": testOTPKeyType,
"default_user": testUserName,
"cidr_list": testCIDRList,
}
resp1 := map[string]interface{}{}
resp2 := map[string]interface{}{
"keys": []string{testOTPRoleName},
"key_info": map[string]interface{}{
testOTPRoleName: map[string]interface{}{
"key_type": testOTPKeyType,
},
},
}
logicaltest.Test(t, logicaltest.TestCase{
Factory: testingFactory,
Steps: []logicaltest.TestStep{
testRoleList(t, resp1),
testRoleWrite(t, testOTPRoleName, testOTPRoleData),
testRoleList(t, resp2),
},
})
}
func TestSSHBackend_DynamicKeyCreate(t *testing.T) { func TestSSHBackend_DynamicKeyCreate(t *testing.T) {
testDynamicRoleData := map[string]interface{}{ testDynamicRoleData := map[string]interface{}{
"key_type": testDynamicKeyType, "key_type": testDynamicKeyType,
@ -962,6 +987,25 @@ func testRoleWrite(t *testing.T, name string, data map[string]interface{}) logic
} }
} }
func testRoleList(t *testing.T, expected map[string]interface{}) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ListOperation,
Path: "roles",
Check: func(resp *logical.Response) error {
if resp == nil {
return fmt.Errorf("nil response")
}
if resp.Data == nil {
return fmt.Errorf("nil data")
}
if !reflect.DeepEqual(resp.Data, expected) {
return fmt.Errorf("Invalid response:\nactual:%#v\nexpected is %#v", resp.Data, expected)
}
return nil
},
}
}
func testRoleRead(t *testing.T, roleName string, expected map[string]interface{}) logicaltest.TestStep { func testRoleRead(t *testing.T, roleName string, expected map[string]interface{}) logicaltest.TestStep {
return logicaltest.TestStep{ return logicaltest.TestStep{
Operation: logical.ReadOperation, Operation: logical.ReadOperation,

View file

@ -488,37 +488,23 @@ func (b *backend) getRole(s logical.Storage, n string) (*sshRole, error) {
return &result, nil return &result, nil
} }
func (b *backend) pathRoleList(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { // parseRole converts a sshRole object into its map[string]interface representation,
entries, err := req.Storage.List("roles/") // with appropriate values for each KeyType. If the KeyType is invalid, it will retun
if err != nil { // an error.
return nil, err func (b *backend) parseRole(role *sshRole) (map[string]interface{}, error) {
} var result map[string]interface{}
return logical.ListResponse(entries), nil switch role.KeyType {
} case KeyTypeOTP:
result = map[string]interface{}{
func (b *backend) pathRoleRead(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
role, err := b.getRole(req.Storage, d.Get("role").(string))
if err != nil {
return nil, err
}
if role == nil {
return nil, nil
}
// Return information should be based on the key type of the role
if role.KeyType == KeyTypeOTP {
return &logical.Response{
Data: map[string]interface{}{
"default_user": role.DefaultUser, "default_user": role.DefaultUser,
"cidr_list": role.CIDRList, "cidr_list": role.CIDRList,
"exclude_cidr_list": role.ExcludeCIDRList, "exclude_cidr_list": role.ExcludeCIDRList,
"key_type": role.KeyType, "key_type": role.KeyType,
"port": role.Port, "port": role.Port,
"allowed_users": role.AllowedUsers, "allowed_users": role.AllowedUsers,
}, }
}, nil case KeyTypeCA:
} else if role.KeyType == KeyTypeCA {
ttl, err := parseutil.ParseDurationSecond(role.TTL) ttl, err := parseutil.ParseDurationSecond(role.TTL)
if err != nil { if err != nil {
return nil, err return nil, err
@ -528,8 +514,7 @@ func (b *backend) pathRoleRead(req *logical.Request, d *framework.FieldData) (*l
return nil, err return nil, err
} }
return &logical.Response{ result = map[string]interface{}{
Data: map[string]interface{}{
"allowed_users": role.AllowedUsers, "allowed_users": role.AllowedUsers,
"allowed_domains": role.AllowedDomains, "allowed_domains": role.AllowedDomains,
"default_user": role.DefaultUser, "default_user": role.DefaultUser,
@ -546,11 +531,9 @@ func (b *backend) pathRoleRead(req *logical.Request, d *framework.FieldData) (*l
"key_type": role.KeyType, "key_type": role.KeyType,
"default_critical_options": role.DefaultCriticalOptions, "default_critical_options": role.DefaultCriticalOptions,
"default_extensions": role.DefaultExtensions, "default_extensions": role.DefaultExtensions,
}, }
}, nil case KeyTypeDynamic:
} else { result = map[string]interface{}{
return &logical.Response{
Data: map[string]interface{}{
"key": role.KeyName, "key": role.KeyName,
"admin_user": role.AdminUser, "admin_user": role.AdminUser,
"default_user": role.DefaultUser, "default_user": role.DefaultUser,
@ -566,9 +549,73 @@ func (b *backend) pathRoleRead(req *logical.Request, d *framework.FieldData) (*l
// being used to install the key. If there is some problem, // being used to install the key. If there is some problem,
// the script can be modified and configured by clients. // the script can be modified and configured by clients.
"install_script": role.InstallScript, "install_script": role.InstallScript,
},
}, nil
} }
default:
return nil, fmt.Errorf("invalid key type: %v", role.KeyType)
}
return result, nil
}
func (b *backend) pathRoleList(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
entries, err := req.Storage.List("roles/")
if err != nil {
return nil, err
}
keyInfo := map[string]interface{}{}
for _, entry := range entries {
role, err := b.getRole(req.Storage, entry)
if err != nil {
// On error, log warning and continue
if b.Logger().IsWarn() {
b.Logger().Warn("ssh: error getting role info", "role", entry, "error", err)
}
continue
}
if role == nil {
// On empty role, log warning and continue
if b.Logger().IsWarn() {
b.Logger().Warn("ssh: no role info found", "role", entry)
}
continue
}
roleInfo, err := b.parseRole(role)
if err != nil {
if b.Logger().IsWarn() {
b.Logger().Warn("ssh: error parsing role info", "role", entry, "error", err)
}
continue
}
if keyType, ok := roleInfo["key_type"]; ok {
keyInfo[entry] = map[string]interface{}{
"key_type": keyType,
}
}
}
return logical.ListResponseWithInfo(entries, keyInfo), nil
}
func (b *backend) pathRoleRead(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
role, err := b.getRole(req.Storage, d.Get("role").(string))
if err != nil {
return nil, err
}
if role == nil {
return nil, nil
}
roleInfo, err := b.parseRole(role)
if err != nil {
return nil, err
}
return &logical.Response{
Data: roleInfo,
}, nil
} }
func (b *backend) pathRoleDelete(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { func (b *backend) pathRoleDelete(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {

View file

@ -110,3 +110,23 @@ func ListResponse(keys []string) *Response {
} }
return resp return resp
} }
// ListResponseWithInfo is used to format a response to a list operation and
// return the keys as well as a map with corresponding key info.
func ListResponseWithInfo(keys []string, keyInfo map[string]interface{}) *Response {
resp := ListResponse(keys)
keyInfoData := make(map[string]interface{})
for _, key := range keys {
val, ok := keyInfo[key]
if ok {
keyInfoData[key] = val
}
}
if len(keyInfoData) > 0 {
resp.Data["key_info"] = keyInfoData
}
return resp
}