Skip to content

Commit 3b9ffeb

Browse files
committed
feat(cli): Add support for password credential in redis helper
1 parent 33fa943 commit 3b9ffeb

File tree

14 files changed

+434
-67
lines changed

14 files changed

+434
-67
lines changed

api/proxy/credentials.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
const (
1414
usernamePasswordCredentialType = "username_password"
15+
passwordCredentialType = "password"
1516
sshPrivateKeyCredentialType = "ssh_private_key"
1617
)
1718

@@ -26,6 +27,16 @@ type UsernamePassword struct {
2627
Consumed bool
2728
}
2829

30+
// Password contains a password credential
31+
type Password struct {
32+
Password string `mapstructure:"password"`
33+
34+
Raw *targets.SessionCredential
35+
// Consumed can be set by the caller to indicate that the credential has
36+
// been used, e.g. displayed to the user
37+
Consumed bool
38+
}
39+
2940
// SshPrivateKey contains the username and private key with optional passphrase
3041
// for the key
3142
type SshPrivateKey struct {
@@ -41,6 +52,7 @@ type SshPrivateKey struct {
4152

4253
type Credentials struct {
4354
UsernamePassword []UsernamePassword
55+
Password []Password
4456
SshPrivateKey []SshPrivateKey
4557
// Unspecified are credentials that do not match one of the types above
4658
Unspecified []*targets.SessionCredential
@@ -62,6 +74,11 @@ func (c Credentials) UnconsumedSessionCredentials() []*targets.SessionCredential
6274
out = append(out, c.Raw)
6375
}
6476
}
77+
for _, c := range c.Password {
78+
if !c.Consumed {
79+
out = append(out, c.Raw)
80+
}
81+
}
6582
return out
6683
}
6784

@@ -76,6 +93,7 @@ func ParseCredentials(creds []*targets.SessionCredential) (Credentials, error) {
7693
}
7794

7895
var upCred UsernamePassword
96+
var pCred Password
7997
var spkCred SshPrivateKey
8098
switch cred.CredentialSource.CredentialType {
8199
case usernamePasswordCredentialType:
@@ -90,6 +108,18 @@ func ParseCredentials(creds []*targets.SessionCredential) (Credentials, error) {
90108
continue
91109
}
92110

111+
case passwordCredentialType:
112+
// Decode attributes from credential struct
113+
if err := mapstructure.Decode(cred.Credential, &pCred); err != nil {
114+
return Credentials{}, err
115+
}
116+
117+
if pCred.Password != "" {
118+
pCred.Raw = cred
119+
out.Password = append(out.Password, pCred)
120+
continue
121+
}
122+
93123
case sshPrivateKeyCredentialType:
94124
// Decode attributes from credential struct
95125
if err := mapstructure.Decode(cred.Credential, &spkCred); err != nil {
@@ -116,6 +146,16 @@ func ParseCredentials(creds []*targets.SessionCredential) (Credentials, error) {
116146
continue
117147
}
118148

149+
// Attempt unmarshaling into password creds
150+
if err := mapstructure.Decode(cred.Secret.Decoded, &pCred); err != nil {
151+
return Credentials{}, err
152+
}
153+
if pCred.Password != "" {
154+
pCred.Raw = cred
155+
out.Password = append(out.Password, pCred)
156+
continue
157+
}
158+
119159
// Attempt unmarshaling into ssh private key creds
120160
if err := mapstructure.Decode(cred.Secret.Decoded, &spkCred); err != nil {
121161
return Credentials{}, err

api/proxy/credentials_test.go

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ var (
2929
},
3030
}
3131

32+
typedPassword = &targets.SessionCredential{
33+
CredentialSource: &targets.CredentialSource{
34+
CredentialType: passwordCredentialType,
35+
},
36+
Credential: map[string]any{
37+
"password": "pass",
38+
},
39+
}
40+
3241
typedSshPrivateKey = &targets.SessionCredential{
3342
CredentialSource: &targets.CredentialSource{
3443
CredentialType: sshPrivateKeyCredentialType,
@@ -75,6 +84,17 @@ var (
7584
},
7685
}
7786

87+
vaultPassword = &targets.SessionCredential{
88+
CredentialSource: &targets.CredentialSource{
89+
Type: vaultGenericLibrarySubtype,
90+
},
91+
Secret: &targets.SessionSecret{
92+
Decoded: map[string]any{
93+
"password": "vault-decoded-pass",
94+
},
95+
},
96+
}
97+
7898
vaultSshPrivateKey = &targets.SessionCredential{
7999
CredentialSource: &targets.CredentialSource{
80100
Type: vaultGenericLibrarySubtype,
@@ -119,6 +139,17 @@ var (
119139
},
120140
}
121141

142+
staticPassword = &targets.SessionCredential{
143+
CredentialSource: &targets.CredentialSource{
144+
Type: staticSubtype,
145+
},
146+
Secret: &targets.SessionSecret{
147+
Decoded: map[string]any{
148+
"password": "static-decoded-pass",
149+
},
150+
},
151+
}
152+
122153
staticSshPrivateKey = &targets.SessionCredential{
123154
CredentialSource: &targets.CredentialSource{
124155
Type: staticSubtype,
@@ -215,6 +246,21 @@ func Test_parseCredentials(t *testing.T) {
215246
},
216247
wantErr: false,
217248
},
249+
{
250+
name: "password-typed",
251+
creds: []*targets.SessionCredential{
252+
typedPassword,
253+
},
254+
wantCreds: Credentials{
255+
Password: []Password{
256+
{
257+
Password: "pass",
258+
Raw: typedPassword,
259+
},
260+
},
261+
},
262+
wantErr: false,
263+
},
218264
{
219265
name: "ssh-private-key-typed",
220266
creds: []*targets.SessionCredential{
@@ -247,6 +293,21 @@ func Test_parseCredentials(t *testing.T) {
247293
},
248294
wantErr: false,
249295
},
296+
{
297+
name: "vault-password-decoded",
298+
creds: []*targets.SessionCredential{
299+
vaultPassword,
300+
},
301+
wantCreds: Credentials{
302+
Password: []Password{
303+
{
304+
Password: "vault-decoded-pass",
305+
Raw: vaultPassword,
306+
},
307+
},
308+
},
309+
wantErr: false,
310+
},
250311
{
251312
name: "vault-private-key-decoded",
252313
creds: []*targets.SessionCredential{
@@ -343,6 +404,21 @@ func Test_parseCredentials(t *testing.T) {
343404
},
344405
wantErr: false,
345406
},
407+
{
408+
name: "static-password-decoded",
409+
creds: []*targets.SessionCredential{
410+
staticPassword,
411+
},
412+
wantCreds: Credentials{
413+
Password: []Password{
414+
{
415+
Password: "static-decoded-pass",
416+
Raw: staticPassword,
417+
},
418+
},
419+
},
420+
wantErr: false,
421+
},
346422
{
347423
name: "static-private-key-decoded",
348424
creds: []*targets.SessionCredential{
@@ -388,7 +464,7 @@ func Test_parseCredentials(t *testing.T) {
388464
creds: []*targets.SessionCredential{
389465
staticSshPrivateKey, UnspecifiedCred1, vaultSshPrivateKey, typedUsernamePassword,
390466
UnspecifiedCred, vaultUsernamePassword, typedSshPrivateKey, staticUsernamePassword,
391-
staticKv,
467+
staticKv, typedPassword, vaultPassword, staticPassword,
392468
},
393469
wantCreds: Credentials{
394470
SshPrivateKey: []SshPrivateKey{
@@ -425,6 +501,20 @@ func Test_parseCredentials(t *testing.T) {
425501
Raw: typedUsernamePassword,
426502
},
427503
},
504+
Password: []Password{
505+
{
506+
Password: "static-decoded-pass",
507+
Raw: staticPassword,
508+
},
509+
{
510+
Password: "vault-decoded-pass",
511+
Raw: vaultPassword,
512+
},
513+
{
514+
Password: "pass",
515+
Raw: typedPassword,
516+
},
517+
},
428518
Unspecified: []*targets.SessionCredential{
429519
UnspecifiedCred, UnspecifiedCred1, staticKv,
430520
},
@@ -447,6 +537,7 @@ func Test_parseCredentials(t *testing.T) {
447537
assert.ElementsMatch(tt.wantCreds.UsernamePassword, creds.UsernamePassword)
448538
assert.ElementsMatch(tt.wantCreds.SshPrivateKey, creds.SshPrivateKey)
449539
assert.ElementsMatch(tt.wantCreds.Unspecified, creds.Unspecified)
540+
assert.ElementsMatch(tt.wantCreds.Password, creds.Password)
450541
})
451542
}
452543
}
@@ -507,6 +598,29 @@ func Test_unconsumedSessionCredentials(t *testing.T) {
507598
},
508599
wantCreds: nil,
509600
},
601+
{
602+
name: "p",
603+
creds: Credentials{
604+
Password: []Password{
605+
{
606+
Raw: vaultPassword,
607+
},
608+
},
609+
},
610+
wantCreds: []*targets.SessionCredential{vaultPassword},
611+
},
612+
{
613+
name: "p-consumed",
614+
creds: Credentials{
615+
Password: []Password{
616+
{
617+
Raw: vaultPassword,
618+
Consumed: true,
619+
},
620+
},
621+
},
622+
wantCreds: nil,
623+
},
510624
{
511625
name: "Unspecified",
512626
creds: Credentials{
@@ -542,10 +656,23 @@ func Test_unconsumedSessionCredentials(t *testing.T) {
542656
Consumed: true,
543657
},
544658
},
659+
Password: []Password{
660+
{
661+
Raw: staticPassword,
662+
},
663+
{
664+
Raw: vaultPassword,
665+
},
666+
{
667+
Raw: typedPassword,
668+
Consumed: true,
669+
},
670+
},
545671
Unspecified: []*targets.SessionCredential{UnspecifiedCred, UnspecifiedCred1},
546672
},
547673
wantCreds: []*targets.SessionCredential{
548674
vaultSshPrivateKey, typedSshPrivateKey, vaultUsernamePassword, UnspecifiedCred, UnspecifiedCred1,
675+
staticPassword, vaultPassword,
549676
},
550677
},
551678
{
@@ -579,6 +706,20 @@ func Test_unconsumedSessionCredentials(t *testing.T) {
579706
Consumed: true,
580707
},
581708
},
709+
Password: []Password{
710+
{
711+
Raw: staticPassword,
712+
Consumed: true,
713+
},
714+
{
715+
Raw: vaultPassword,
716+
Consumed: true,
717+
},
718+
{
719+
Raw: typedPassword,
720+
Consumed: true,
721+
},
722+
},
582723
Unspecified: []*targets.SessionCredential{UnspecifiedCred, UnspecifiedCred1},
583724
},
584725
wantCreds: []*targets.SessionCredential{
@@ -610,11 +751,23 @@ func Test_unconsumedSessionCredentials(t *testing.T) {
610751
Raw: typedUsernamePassword,
611752
},
612753
},
754+
Password: []Password{
755+
{
756+
Raw: staticPassword,
757+
},
758+
{
759+
Raw: vaultPassword,
760+
},
761+
{
762+
Raw: typedPassword,
763+
},
764+
},
613765
Unspecified: []*targets.SessionCredential{UnspecifiedCred, UnspecifiedCred1},
614766
},
615767
wantCreds: []*targets.SessionCredential{
616768
staticSshPrivateKey, UnspecifiedCred1, vaultSshPrivateKey, typedUsernamePassword,
617769
UnspecifiedCred, vaultUsernamePassword, typedSshPrivateKey, staticUsernamePassword,
770+
staticPassword, vaultPassword, typedPassword,
618771
},
619772
},
620773
}

internal/cmd/commands/connect/redis.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,21 @@ func (r *redisFlags) buildArgs(c *Command, port, ip, _ string, creds proxy.Crede
4949
var username, password string
5050

5151
retCreds = creds
52-
if len(retCreds.UsernamePassword) > 0 {
52+
switch {
53+
case len(retCreds.UsernamePassword) > 0:
5354
// Mark credential as consumed, such that it is not printed to the user
5455
retCreds.UsernamePassword[0].Consumed = true
5556

5657
// Grab the first available username/password credential brokered
5758
username = retCreds.UsernamePassword[0].Username
5859
password = retCreds.UsernamePassword[0].Password
60+
61+
case len(retCreds.Password) > 0:
62+
// Mark credential as consumed, such that it is not printed to the user
63+
retCreds.Password[0].Consumed = true
64+
65+
// Grab the first available password credential brokered
66+
password = retCreds.Password[0].Password
5967
}
6068

6169
switch r.flagRedisStyle {
@@ -69,12 +77,14 @@ func (r *redisFlags) buildArgs(c *Command, port, ip, _ string, creds proxy.Crede
6977
case username != "":
7078
args = append(args, "--user", username)
7179
case c.flagUsername != "":
72-
args = append(args, "--user", c.flagUsername, "--askpass")
80+
args = append(args, "--user", c.flagUsername)
7381
}
7482

75-
// Password is read by redis-cli via environment variable. The password disappears after the command exits.
7683
if password != "" {
7784
envs = append(envs, fmt.Sprintf("REDISCLI_AUTH=%s", password))
85+
} else {
86+
// prompt for password if it wasn't provided
87+
envs = append(envs, "--askpass")
7888
}
7989
}
8090

0 commit comments

Comments
 (0)