Skip to content
This repository was archived by the owner on Jan 17, 2025. It is now read-only.

Commit 004a620

Browse files
Use md5 hashing for redshift_user passwords
1 parent 8c558d1 commit 004a620

3 files changed

Lines changed: 62 additions & 2 deletions

File tree

redshift/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,9 @@ func (c *Config) Client() (*Client, error) {
131131

132132
return &client, nil
133133
}
134+
135+
func (c *Client) Close() {
136+
if c.db != nil {
137+
c.db.Close()
138+
}
139+
}

redshift/resource_redshift_user.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package redshift
22

33
import (
4+
"crypto/md5"
45
"database/sql"
56
"fmt"
67
"log"
@@ -163,6 +164,7 @@ func resourceRedshiftUserCreate(db *DBConnection, d *schema.ResourceData) error
163164
{userCreateDBAttr, "CREATEDB", "NOCREATEDB"},
164165
}
165166

167+
userName := d.Get(userNameAttr).(string)
166168
createOpts := make([]string, 0, len(stringOpts)+len(intOpts)+len(boolOpts))
167169
for _, opt := range stringOpts {
168170
v, ok := d.GetOk(opt.hclKey)
@@ -186,7 +188,7 @@ func resourceRedshiftUserCreate(db *DBConnection, d *schema.ResourceData) error
186188
if val != "" {
187189
switch {
188190
case opt.hclKey == userPasswordAttr:
189-
createOpts = append(createOpts, fmt.Sprintf("%s '%s'", opt.sqlKey, pqQuoteLiteral(val)))
191+
createOpts = append(createOpts, fmt.Sprintf("%s '%s'", opt.sqlKey, md5Password(userName, val)))
190192
case opt.hclKey == userValidUntilAttr:
191193
switch {
192194
case v.(string) == "", strings.ToLower(v.(string)) == "infinity":
@@ -216,7 +218,6 @@ func resourceRedshiftUserCreate(db *DBConnection, d *schema.ResourceData) error
216218
createOpts = append(createOpts, valStr)
217219
}
218220

219-
userName := d.Get(userNameAttr).(string)
220221
createStr := strings.Join(createOpts, " ")
221222
sql := fmt.Sprintf("CREATE USER %s WITH %s", pq.QuoteIdentifier(userName), createStr)
222223

@@ -594,3 +595,13 @@ func getDefaultSyslogAccess(d *schema.ResourceData) string {
594595

595596
return defaultUserSyslogAccess
596597
}
598+
599+
// Generates an md5 password for the user.
600+
// Per https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_USER.html,
601+
// the process is:
602+
// 1. concatenate the password and username
603+
// 2. convert the concatenated string to an md5 hash in hex format
604+
// 3. prefix the result with 'md5' (unquoted)
605+
func md5Password(userName string, password string) string {
606+
return fmt.Sprintf("md5%x", md5.Sum([]byte(fmt.Sprintf("%s%s", password, userName))))
607+
}

redshift/resource_redshift_user_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package redshift
33
import (
44
"database/sql"
55
"fmt"
6+
"os"
7+
"strconv"
68
"strings"
79
"testing"
810

@@ -24,6 +26,7 @@ func TestAccRedshiftUser_Basic(t *testing.T) {
2426

2527
testAccCheckRedshiftUserExists("John-and-Jane.doe@example.com"),
2628
resource.TestCheckResourceAttr("redshift_user.with_email", "name", "john-and-jane.doe@example.com"),
29+
testAccCheckRedshiftUserCanLogin("John-and-Jane.doe@example.com", "Foobarbaz1"),
2730

2831
testAccCheckRedshiftUserExists("user_defaults"),
2932
resource.TestCheckResourceAttr("redshift_user.user_with_defaults", "name", "user_defaults"),
@@ -284,3 +287,43 @@ func TestPermanentUsername(t *testing.T) {
284287
t.Fatalf("permanentUsername should strip \"IAMA:\" prefix. Expected %s but was %s", expected, result)
285288
}
286289
}
290+
291+
func testAccCheckRedshiftUserCanLogin(user string, password string) resource.TestCheckFunc {
292+
return func(s *terraform.State) error {
293+
// there doesn't seem to be a good way to extract the provider configuration
294+
// at runtime. However we know we've configured the provider with default settings
295+
// so we can mimic the same behavior
296+
port, ok := os.LookupEnv("REDSHIFT_PORT")
297+
if !ok {
298+
port = "5439"
299+
}
300+
portNum, err := strconv.Atoi(port)
301+
if err != nil {
302+
return fmt.Errorf("Unable to check if user can login due to bad REDSHIFT_PORT: %s", err)
303+
}
304+
database, ok := os.LookupEnv("REDSHIFT_DATABASE")
305+
if !ok {
306+
database = "redshift"
307+
}
308+
sslMode, ok := os.LookupEnv("REDSHIFT_SSLMODE")
309+
if !ok {
310+
sslMode = "require"
311+
}
312+
config := &Config{
313+
Host: os.Getenv("REDSHIFT_HOST"),
314+
Port: portNum,
315+
Username: user,
316+
Password: password,
317+
Database: database,
318+
SSLMode: sslMode,
319+
MaxConns: defaultProviderMaxOpenConnections,
320+
}
321+
322+
client, err := config.Client()
323+
if err != nil {
324+
return fmt.Errorf("User is unable to login %s", err)
325+
}
326+
defer client.Close()
327+
return nil
328+
}
329+
}

0 commit comments

Comments
 (0)