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) {
testDynamicRoleData := map[string]interface{}{
"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 {
return logicaltest.TestStep{
Operation: logical.ReadOperation,

View File

@ -488,13 +488,115 @@ func (b *backend) getRole(s logical.Storage, n string) (*sshRole, error) {
return &result, nil
}
// parseRole converts a sshRole object into its map[string]interface representation,
// with appropriate values for each KeyType. If the KeyType is invalid, it will retun
// an error.
func (b *backend) parseRole(role *sshRole) (map[string]interface{}, error) {
var result map[string]interface{}
switch role.KeyType {
case KeyTypeOTP:
result = map[string]interface{}{
"default_user": role.DefaultUser,
"cidr_list": role.CIDRList,
"exclude_cidr_list": role.ExcludeCIDRList,
"key_type": role.KeyType,
"port": role.Port,
"allowed_users": role.AllowedUsers,
}
case KeyTypeCA:
ttl, err := parseutil.ParseDurationSecond(role.TTL)
if err != nil {
return nil, err
}
maxTTL, err := parseutil.ParseDurationSecond(role.MaxTTL)
if err != nil {
return nil, err
}
result = map[string]interface{}{
"allowed_users": role.AllowedUsers,
"allowed_domains": role.AllowedDomains,
"default_user": role.DefaultUser,
"ttl": int64(ttl.Seconds()),
"max_ttl": int64(maxTTL.Seconds()),
"allowed_critical_options": role.AllowedCriticalOptions,
"allowed_extensions": role.AllowedExtensions,
"allow_user_certificates": role.AllowUserCertificates,
"allow_host_certificates": role.AllowHostCertificates,
"allow_bare_domains": role.AllowBareDomains,
"allow_subdomains": role.AllowSubdomains,
"allow_user_key_ids": role.AllowUserKeyIDs,
"key_id_format": role.KeyIDFormat,
"key_type": role.KeyType,
"default_critical_options": role.DefaultCriticalOptions,
"default_extensions": role.DefaultExtensions,
}
case KeyTypeDynamic:
result = map[string]interface{}{
"key": role.KeyName,
"admin_user": role.AdminUser,
"default_user": role.DefaultUser,
"cidr_list": role.CIDRList,
"exclude_cidr_list": role.ExcludeCIDRList,
"port": role.Port,
"key_type": role.KeyType,
"key_bits": role.KeyBits,
"allowed_users": role.AllowedUsers,
"key_option_specs": role.KeyOptionSpecs,
// Returning install script will make the output look messy.
// But this is one way for clients to see the script that is
// being used to install the key. If there is some problem,
// the script can be modified and configured by clients.
"install_script": role.InstallScript,
}
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
}
return logical.ListResponse(entries), nil
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) {
@ -506,69 +608,14 @@ func (b *backend) pathRoleRead(req *logical.Request, d *framework.FieldData) (*l
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,
"cidr_list": role.CIDRList,
"exclude_cidr_list": role.ExcludeCIDRList,
"key_type": role.KeyType,
"port": role.Port,
"allowed_users": role.AllowedUsers,
},
}, nil
} else if role.KeyType == KeyTypeCA {
ttl, err := parseutil.ParseDurationSecond(role.TTL)
if err != nil {
return nil, err
}
maxTTL, err := parseutil.ParseDurationSecond(role.MaxTTL)
if err != nil {
return nil, err
}
return &logical.Response{
Data: map[string]interface{}{
"allowed_users": role.AllowedUsers,
"allowed_domains": role.AllowedDomains,
"default_user": role.DefaultUser,
"ttl": int64(ttl.Seconds()),
"max_ttl": int64(maxTTL.Seconds()),
"allowed_critical_options": role.AllowedCriticalOptions,
"allowed_extensions": role.AllowedExtensions,
"allow_user_certificates": role.AllowUserCertificates,
"allow_host_certificates": role.AllowHostCertificates,
"allow_bare_domains": role.AllowBareDomains,
"allow_subdomains": role.AllowSubdomains,
"allow_user_key_ids": role.AllowUserKeyIDs,
"key_id_format": role.KeyIDFormat,
"key_type": role.KeyType,
"default_critical_options": role.DefaultCriticalOptions,
"default_extensions": role.DefaultExtensions,
},
}, nil
} else {
return &logical.Response{
Data: map[string]interface{}{
"key": role.KeyName,
"admin_user": role.AdminUser,
"default_user": role.DefaultUser,
"cidr_list": role.CIDRList,
"exclude_cidr_list": role.ExcludeCIDRList,
"port": role.Port,
"key_type": role.KeyType,
"key_bits": role.KeyBits,
"allowed_users": role.AllowedUsers,
"key_option_specs": role.KeyOptionSpecs,
// Returning install script will make the output look messy.
// But this is one way for clients to see the script that is
// being used to install the key. If there is some problem,
// the script can be modified and configured by clients.
"install_script": role.InstallScript,
},
}, 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) {

View File

@ -110,3 +110,23 @@ func ListResponse(keys []string) *Response {
}
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
}