Skip to content

Commit 0adf266

Browse files
authoredNov 13, 2024
secrets/ssh: Return the allow_empty_principals field in read api (#28901)
* secrets/ssh: Return the allow_empty_principals field in read api - Return the new field in the read response api and add a test case that will catch these errors in the future of adding a field to the ssh role and not returning it in the read api response * Add cl
1 parent 1196b8e commit 0adf266

File tree

3 files changed

+101
-0
lines changed

3 files changed

+101
-0
lines changed
 

‎builtin/logical/ssh/backend_test.go

+97
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"testing"
1616
"time"
1717

18+
"github.com/hashicorp/go-secure-stdlib/strutil"
1819
"github.com/hashicorp/vault/api"
1920
"github.com/hashicorp/vault/builtin/credential/userpass"
2021
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
@@ -24,6 +25,7 @@ import (
2425
"github.com/hashicorp/vault/sdk/logical"
2526
"github.com/hashicorp/vault/vault"
2627
"github.com/mitchellh/mapstructure"
28+
"github.com/stretchr/testify/assert"
2729
"github.com/stretchr/testify/require"
2830
"golang.org/x/crypto/ssh"
2931
)
@@ -212,6 +214,101 @@ func testSSH(user, host string, auth ssh.AuthMethod, command string) error {
212214
return nil
213215
}
214216

217+
// TestBackend_ReadRolesReturnsAllFields validates we did not forget to return a newly added
218+
// field from the ssh role in the read API.
219+
func TestBackend_ReadRolesReturnsAllFields(t *testing.T) {
220+
t.Parallel()
221+
222+
config := logical.TestBackendConfig()
223+
config.StorageView = &logical.InmemStorage{}
224+
225+
b, err := Backend(config)
226+
require.NoError(t, err, "failed creating backend")
227+
err = b.Setup(context.Background(), config)
228+
require.NoError(t, err, "failed setting up backend")
229+
230+
tests := []struct {
231+
name string
232+
data map[string]interface{}
233+
ignoredFields []string
234+
}{
235+
{
236+
name: "otp",
237+
data: map[string]interface{}{
238+
"key_type": "otp",
239+
"default_user": "ubuntu",
240+
},
241+
ignoredFields: []string{
242+
"allow_host_certificates", "allow_subdomains",
243+
"default_user_template", "allowed_extensions", "allowed_user_key_lengths",
244+
"allow_empty_principals", "ttl", "allowed_domains_template",
245+
"default_extensions_template", "default_critical_options",
246+
"allow_bare_domains", "allowed_domains", "allowed_critical_options",
247+
"allow_user_certificates", "allow_user_key_ids", "algorithm_signer",
248+
"not_before_duration", "max_ttl", "default_extensions", "allowed_users_template", "key_id_format",
249+
},
250+
},
251+
{
252+
name: "ca",
253+
data: map[string]interface{}{
254+
"key_type": "ca",
255+
"algorithm_signer": "rsa-sha2-256",
256+
"allow_user_certificates": true,
257+
},
258+
ignoredFields: []string{"port", "cidr_list", "exclude_cidr_list"},
259+
},
260+
}
261+
262+
for _, tc := range tests {
263+
t.Run(tc.name, func(t *testing.T) {
264+
var fieldsToIgnore []string
265+
fieldsToIgnore = append(fieldsToIgnore, tc.ignoredFields...)
266+
// These fields apply to all use cases, role_version is never returned and
267+
// allowed_user_key_types_lengths is an internal value for the allowed_user_key_lengths field
268+
fieldsToIgnore = append(fieldsToIgnore, "role_version")
269+
fieldsToIgnore = append(fieldsToIgnore, "allowed_user_key_types_lengths")
270+
271+
roleName := fmt.Sprintf("roles/role-%s", tc.name)
272+
req := &logical.Request{
273+
Operation: logical.UpdateOperation,
274+
Path: roleName,
275+
Storage: config.StorageView,
276+
Data: tc.data,
277+
}
278+
resp, err := b.HandleRequest(context.Background(), req)
279+
if err != nil || (resp != nil && resp.IsError()) || resp != nil {
280+
t.Fatalf("failed to create role %s: resp:%#v err:%s", roleName, resp, err)
281+
}
282+
283+
req = &logical.Request{
284+
Operation: logical.ReadOperation,
285+
Path: roleName,
286+
Storage: config.StorageView,
287+
}
288+
resp, err = b.HandleRequest(context.Background(), req)
289+
if err != nil || (resp != nil && resp.IsError()) || resp == nil {
290+
t.Fatalf("failed to read role %s: resp:%#v err:%s", roleName, resp, err)
291+
}
292+
293+
roleMap := map[string]interface{}{}
294+
err = mapstructure.Decode(sshRole{}, &roleMap)
295+
require.NoError(t, err, "failed getting all fields in ssh role")
296+
297+
// Identify missing fields from OTP response
298+
var missingFields []string
299+
for fieldName := range roleMap {
300+
if _, ok := resp.Data[fieldName]; !ok {
301+
if strutil.StrListContains(fieldsToIgnore, fieldName) {
302+
continue
303+
}
304+
missingFields = append(missingFields, fieldName)
305+
}
306+
}
307+
assert.Empty(t, missingFields, "response was missing fields: %s", missingFields)
308+
})
309+
}
310+
}
311+
215312
func TestBackend_AllowedUsers(t *testing.T) {
216313
config := logical.TestBackendConfig()
217314
config.StorageView = &logical.InmemStorage{}

‎builtin/logical/ssh/path_roles.go

+1
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,7 @@ func (b *backend) parseRole(role *sshRole) (map[string]interface{}, error) {
698698
"allowed_user_key_lengths": role.AllowedUserKeyTypesLengths,
699699
"algorithm_signer": role.AlgorithmSigner,
700700
"not_before_duration": int64(role.NotBeforeDuration.Seconds()),
701+
"allow_empty_principals": role.AllowEmptyPrincipals,
701702
}
702703
case KeyTypeDynamic:
703704
return nil, fmt.Errorf("dynamic key type roles are no longer supported")

‎changelog/28901.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
secrets/ssh: Return the flag `allow_empty_principals` in the read role api when key_type is "ca"
3+
```

0 commit comments

Comments
 (0)