Skip to content

Commit 997c3ce

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

File tree

12 files changed

+215
-59
lines changed

12 files changed

+215
-59
lines changed

api/proxy/credentials.go

Lines changed: 13 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

@@ -90,6 +91,18 @@ func ParseCredentials(creds []*targets.SessionCredential) (Credentials, error) {
9091
continue
9192
}
9293

94+
case passwordCredentialType:
95+
// Decode attributes from credential struct
96+
if err := mapstructure.Decode(cred.Credential, &upCred); err != nil {
97+
return Credentials{}, err
98+
}
99+
100+
if upCred.Password != "" {
101+
upCred.Raw = cred
102+
out.UsernamePassword = append(out.UsernamePassword, upCred)
103+
continue
104+
}
105+
93106
case sshPrivateKeyCredentialType:
94107
// Decode attributes from credential struct
95108
if err := mapstructure.Decode(cred.Credential, &spkCred); err != nil {

internal/cmd/commands/connect/redis.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,14 @@ func (r *redisFlags) buildArgs(c *Command, port, ip, _ string, creds proxy.Crede
6969
case username != "":
7070
args = append(args, "--user", username)
7171
case c.flagUsername != "":
72-
args = append(args, "--user", c.flagUsername, "--askpass")
72+
args = append(args, "--user", c.flagUsername)
7373
}
7474

75-
// Password is read by redis-cli via environment variable. The password disappears after the command exits.
7675
if password != "" {
7776
envs = append(envs, fmt.Sprintf("REDISCLI_AUTH=%s", password))
77+
} else {
78+
// prompt for password if it wasn't provided
79+
envs = append(envs, "--askpass")
7880
}
7981
}
8082

testing/internal/e2e/boundary/credential.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,10 @@ func CreateStaticCredentialPrivateKeyCli(t testing.TB, ctx context.Context, cred
202202
return credentialId, nil
203203
}
204204

205-
// CreateStaticCredentialPasswordCli uses the cli to create a new password credential in the
205+
// CreateStaticCredentialUsernamePasswordCli uses the cli to create a new password credential in the
206206
// provided static credential store.
207207
// Returns the id of the new credential
208-
func CreateStaticCredentialPasswordCli(t testing.TB, ctx context.Context, credentialStoreId string, user string, password string) (string, error) {
208+
func CreateStaticCredentialUsernamePasswordCli(t testing.TB, ctx context.Context, credentialStoreId string, user string, password string) (string, error) {
209209
name, err := base62.Random(16)
210210
if err != nil {
211211
return "", err
@@ -238,6 +238,41 @@ func CreateStaticCredentialPasswordCli(t testing.TB, ctx context.Context, creden
238238
return credentialId, nil
239239
}
240240

241+
// CreateStaticCredentialPasswordCli uses the cli to create a new password credential in the
242+
// provided static credential store.
243+
// Returns the id of the new credential
244+
func CreateStaticCredentialPasswordCli(t testing.TB, ctx context.Context, credentialStoreId string, password string) (string, error) {
245+
name, err := base62.Random(16)
246+
if err != nil {
247+
return "", err
248+
}
249+
250+
output := e2e.RunCommand(ctx, "boundary",
251+
e2e.WithArgs(
252+
"credentials", "create", "password",
253+
"-credential-store-id", credentialStoreId,
254+
"-password", "env://E2E_CREDENTIALS_PASSWORD",
255+
"-name", fmt.Sprintf("e2e Credential %s", name),
256+
"-description", "e2e",
257+
"-format", "json",
258+
),
259+
e2e.WithEnv("E2E_CREDENTIALS_PASSWORD", password),
260+
)
261+
if output.Err != nil {
262+
return "", fmt.Errorf("%w: %s", output.Err, string(output.Stderr))
263+
}
264+
265+
var createCredentialsResult credentials.CredentialCreateResult
266+
err = json.Unmarshal(output.Stdout, &createCredentialsResult)
267+
if err != nil {
268+
return "", err
269+
}
270+
271+
credentialId := createCredentialsResult.Item.Id
272+
t.Logf("Created Password Credential: %s", credentialId)
273+
return credentialId, nil
274+
}
275+
241276
// CreateStaticCredentialPasswordDomainCli uses the cli to create a new username password domain credential in the
242277
// provided static credential store.
243278
// Returns the id of the new credential

testing/internal/e2e/infra/docker.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,9 +599,19 @@ func setupRedisAuthAndUser(t testing.TB, resource *dockertest.Resource, pool *do
599599
t.Helper()
600600
t.Log("Configuring Redis authentication and user permissions...")
601601

602+
// e2e user
602603
err := exec.Command(
603604
"docker", "exec", config.NetworkAlias, "redis-cli",
604-
"ACL", "SETUSER", config.User, "on", fmt.Sprintf(">%s", config.Password), "+@read", "+@write", "allkeys",
605+
"ACL", "SETUSER", config.User, "on", fmt.Sprintf(">%s", config.Password), "+@all", "allkeys",
606+
).Run()
607+
if err != nil {
608+
return err
609+
}
610+
611+
// default user
612+
err = exec.Command(
613+
"docker", "exec", config.NetworkAlias, "redis-cli",
614+
"ACL", "SETUSER", "default", fmt.Sprintf(">%s", config.Password),
605615
).Run()
606616
if err != nil {
607617
return err

testing/internal/e2e/tests/base/credential_store_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func TestCliStaticCredentialStore(t *testing.T) {
7777
require.NoError(t, err)
7878
privateKeyCredentialsId, err := boundary.CreateStaticCredentialPrivateKeyCli(t, ctx, storeId, c.TargetSshUser, testPemFile)
7979
require.NoError(t, err)
80-
pwCredentialId, err := boundary.CreateStaticCredentialPasswordCli(t, ctx, storeId, c.TargetSshUser, testPassword)
80+
pwCredentialId, err := boundary.CreateStaticCredentialUsernamePasswordCli(t, ctx, storeId, c.TargetSshUser, testPassword)
8181
require.NoError(t, err)
8282
jsonCredentialId, err := boundary.CreateStaticCredentialJsonCli(t, ctx, storeId, testCredentialsFile)
8383
require.NoError(t, err)

testing/internal/e2e/tests/base/paginate_credential_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func TestCliPaginateCredentials(t *testing.T) {
8888
assert.Empty(t, initialCredentials.ListToken)
8989

9090
// Create a new credential and destroy one of the other credentials
91-
credentialId, err := boundary.CreateStaticCredentialPasswordCli(t, ctx, storeId, "user", "password")
91+
credentialId, err := boundary.CreateStaticCredentialUsernamePasswordCli(t, ctx, storeId, "user", "password")
9292
require.NoError(t, err)
9393
output = e2e.RunCommand(ctx, "boundary",
9494
e2e.WithArgs(

testing/internal/e2e/tests/base/target_tcp_connect_cassandra_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func TestCliTcpTargetConnectCassandra(t *testing.T) {
8282
storeId, err := boundary.CreateCredentialStoreStaticCli(t, ctx, projectId)
8383
require.NoError(t, err)
8484

85-
credentialId, err := boundary.CreateStaticCredentialPasswordCli(
85+
credentialId, err := boundary.CreateStaticCredentialUsernamePasswordCli(
8686
t,
8787
ctx,
8888
storeId,

testing/internal/e2e/tests/base/target_tcp_connect_mongo_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func TestCliTcpTargetConnectMongo(t *testing.T) {
8383
storeId, err := boundary.CreateCredentialStoreStaticCli(t, ctx, projectId)
8484
require.NoError(t, err)
8585

86-
credentialId, err := boundary.CreateStaticCredentialPasswordCli(
86+
credentialId, err := boundary.CreateStaticCredentialUsernamePasswordCli(
8787
t,
8888
ctx,
8989
storeId,

testing/internal/e2e/tests/base/target_tcp_connect_mysql_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func TestCliTcpTargetConnectMysql(t *testing.T) {
8282
storeId, err := boundary.CreateCredentialStoreStaticCli(t, ctx, projectId)
8383
require.NoError(t, err)
8484

85-
credentialId, err := boundary.CreateStaticCredentialPasswordCli(
85+
credentialId, err := boundary.CreateStaticCredentialUsernamePasswordCli(
8686
t,
8787
ctx,
8888
storeId,

testing/internal/e2e/tests/base/target_tcp_connect_redis_test.go

Lines changed: 143 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,137 @@ import (
1919
"github.com/stretchr/testify/require"
2020
)
2121

22-
// TestCliTcpTargetConnectRedis uses the boundary cli to connect to a target using `connect redis`
23-
func TestCliTcpTargetConnectRedis(t *testing.T) {
22+
type RedisContainerInfo struct {
23+
Hostname string
24+
Port string
25+
Username string
26+
Password string
27+
}
28+
29+
type BoundaryResources struct {
30+
TargetId string
31+
StoreId string
32+
}
33+
34+
// TestCliTcpTargetConnectRedisWithUsernamePassword uses the boundary cli to connect
35+
// to a target using `connect redis` with a username and password
36+
func TestCliTcpTargetConnectRedisWithUsernamePassword(t *testing.T) {
2437
e2e.MaybeSkipTest(t)
2538

26-
pool, err := dockertest.NewPool("")
39+
// Setup
40+
ctx := context.Background()
41+
redisInfo := setupRedisContainer(t, ctx)
42+
resources := setupBoundaryResources(t, ctx, redisInfo)
43+
44+
credentialId, err := boundary.CreateStaticCredentialUsernamePasswordCli(
45+
t,
46+
ctx,
47+
resources.StoreId,
48+
redisInfo.Username,
49+
redisInfo.Password,
50+
)
51+
require.NoError(t, err)
52+
53+
err = boundary.AddBrokeredCredentialSourceToTargetCli(t, ctx, resources.TargetId, credentialId)
2754
require.NoError(t, err)
2855

56+
t.Logf("Attempting to connect to Redis target %s", resources.TargetId)
57+
58+
// Validation
59+
cmd := exec.CommandContext(ctx,
60+
"boundary",
61+
"connect", "redis",
62+
"-target-id", resources.TargetId,
63+
)
64+
65+
stdin, err := cmd.StdinPipe()
66+
require.NoError(t, err)
67+
stdout, err := cmd.StdoutPipe()
68+
require.NoError(t, err)
69+
require.NoError(t, cmd.Start())
70+
71+
output, err := sendRedisCommand(stdin, stdout, "ACL WHOAMI\r\n")
72+
require.NoError(t, err)
73+
require.Equal(t, redisInfo.Username, output)
74+
75+
output, err = sendRedisCommand(stdin, stdout, "SET e2etestkey e2etestvalue\r\n")
76+
require.NoError(t, err)
77+
require.Equal(t, "OK", output)
78+
79+
output, err = sendRedisCommand(stdin, stdout, "GET e2etestkey\r\n")
80+
require.NoError(t, err)
81+
require.Equal(t, "e2etestvalue", output)
82+
83+
output, err = sendRedisCommand(stdin, stdout, "QUIT\r\n")
84+
require.Equal(t, io.EOF, err)
85+
require.Empty(t, output)
86+
87+
// Confirm that boundary connect has closed
88+
err = cmd.Wait()
89+
require.NoError(t, err)
90+
}
91+
92+
// TestCliTcpTargetConnectRedisWithPassword uses the boundary cli to connect
93+
// to a target using `connect redis` with a password
94+
func TestCliTcpTargetConnectRedisWithPassword(t *testing.T) {
95+
e2e.MaybeSkipTest(t)
96+
97+
// Setup
2998
ctx := context.Background()
99+
redisInfo := setupRedisContainer(t, ctx)
100+
resources := setupBoundaryResources(t, ctx, redisInfo)
101+
102+
credentialId, err := boundary.CreateStaticCredentialPasswordCli(
103+
t,
104+
ctx,
105+
resources.StoreId,
106+
redisInfo.Password,
107+
)
108+
require.NoError(t, err)
109+
110+
err = boundary.AddBrokeredCredentialSourceToTargetCli(t, ctx, resources.TargetId, credentialId)
111+
require.NoError(t, err)
112+
113+
t.Logf("Attempting to connect to Redis target %s", resources.TargetId)
114+
115+
// Validation
116+
cmd := exec.CommandContext(ctx,
117+
"boundary",
118+
"connect", "redis",
119+
"-target-id", resources.TargetId,
120+
)
121+
122+
stdin, err := cmd.StdinPipe()
123+
require.NoError(t, err)
124+
stdout, err := cmd.StdoutPipe()
125+
require.NoError(t, err)
126+
require.NoError(t, cmd.Start())
127+
128+
output, err := sendRedisCommand(stdin, stdout, "ACL WHOAMI\r\n")
129+
require.NoError(t, err)
130+
require.Equal(t, "default", output)
131+
132+
output, err = sendRedisCommand(stdin, stdout, "SET e2etestkey e2etestvalue\r\n")
133+
require.NoError(t, err)
134+
require.Equal(t, "OK", output)
135+
136+
output, err = sendRedisCommand(stdin, stdout, "GET e2etestkey\r\n")
137+
require.NoError(t, err)
138+
require.Equal(t, "e2etestvalue", output)
139+
140+
output, err = sendRedisCommand(stdin, stdout, "QUIT\r\n")
141+
require.Equal(t, io.EOF, err)
142+
require.Empty(t, output)
143+
144+
// Confirm that boundary connect has closed
145+
err = cmd.Wait()
146+
require.NoError(t, err)
147+
}
148+
149+
// setupRedisContainer starts a Redis container and returns its connection info
150+
func setupRedisContainer(t *testing.T, ctx context.Context) *RedisContainerInfo {
151+
pool, err := dockertest.NewPool("")
152+
require.NoError(t, err)
30153

31154
network, err := pool.NetworksByName("e2e_cluster")
32155
require.NoError(t, err, "Failed to get e2e_cluster network")
@@ -52,12 +175,22 @@ func TestCliTcpTargetConnectRedis(t *testing.T) {
52175
// Wait for Redis to be ready
53176
err = pool.Retry(func() error {
54177
out, e := exec.CommandContext(ctx, "docker", "exec", hostname,
55-
"redis-cli", "-h", hostname, "-p", port, "PING").CombinedOutput()
178+
"redis-cli", "-h", hostname, "-p", port, "-a", pw, "PING").CombinedOutput()
56179
t.Logf("Redis PING output: %s", out)
57180
return e
58181
})
59182
require.NoError(t, err, "Redis container failed to start")
183+
return &RedisContainerInfo{
184+
Hostname: hostname,
185+
Port: port,
186+
Username: user,
187+
Password: pw,
188+
}
189+
}
60190

191+
// setupBoundaryResources sets up the following Boundary resources:
192+
// Org, Project, Target, Credential Store
193+
func setupBoundaryResources(t *testing.T, ctx context.Context, redisInfo *RedisContainerInfo) *BoundaryResources {
61194
boundary.AuthenticateAdminCli(t, ctx)
62195

63196
orgId, err := boundary.CreateOrgCli(t, ctx)
@@ -76,55 +209,18 @@ func TestCliTcpTargetConnectRedis(t *testing.T) {
76209
t,
77210
ctx,
78211
projectId,
79-
port,
80-
target.WithAddress(hostname),
212+
redisInfo.Port,
213+
target.WithAddress(redisInfo.Hostname),
81214
)
82215
require.NoError(t, err)
83216

84217
storeId, err := boundary.CreateCredentialStoreStaticCli(t, ctx, projectId)
85218
require.NoError(t, err)
86219

87-
credentialId, err := boundary.CreateStaticCredentialPasswordCli(
88-
t,
89-
ctx,
90-
storeId,
91-
user,
92-
pw,
93-
)
94-
require.NoError(t, err)
95-
96-
err = boundary.AddBrokeredCredentialSourceToTargetCli(t, ctx, targetId, credentialId)
97-
require.NoError(t, err)
98-
99-
t.Logf("Attempting to connect to Redis target %s", targetId)
100-
101-
cmd := exec.CommandContext(ctx,
102-
"boundary",
103-
"connect", "redis",
104-
"-target-id", targetId,
105-
)
106-
107-
stdin, err := cmd.StdinPipe()
108-
require.NoError(t, err)
109-
stdout, err := cmd.StdoutPipe()
110-
require.NoError(t, err)
111-
require.NoError(t, cmd.Start())
112-
113-
output, err := sendRedisCommand(stdin, stdout, "SET e2etestkey e2etestvalue\r\n")
114-
require.NoError(t, err)
115-
require.Equal(t, "OK", output)
116-
117-
output, err = sendRedisCommand(stdin, stdout, "GET e2etestkey\r\n")
118-
require.NoError(t, err)
119-
require.Equal(t, "e2etestvalue", output)
120-
121-
output, err = sendRedisCommand(stdin, stdout, "QUIT\r\n")
122-
require.Equal(t, io.EOF, err)
123-
require.Empty(t, output)
124-
125-
// Confirm that boundary connect has closed
126-
err = cmd.Wait()
127-
require.NoError(t, err)
220+
return &BoundaryResources{
221+
TargetId: targetId,
222+
StoreId: storeId,
223+
}
128224
}
129225

130226
func sendRedisCommand(stdin io.WriteCloser, stdout io.ReadCloser, cmdStr string) (string, error) {

0 commit comments

Comments
 (0)