Skip to content

Commit

Permalink
Use md5 hashing for redshift_user passwords
Browse files Browse the repository at this point in the history
  • Loading branch information
sworisbreathing committed Jul 16, 2021
1 parent 8c558d1 commit 004a620
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 2 deletions.
6 changes: 6 additions & 0 deletions redshift/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,9 @@ func (c *Config) Client() (*Client, error) {

return &client, nil
}

func (c *Client) Close() {
if c.db != nil {
c.db.Close()
}
}
15 changes: 13 additions & 2 deletions redshift/resource_redshift_user.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package redshift

import (
"crypto/md5"
"database/sql"
"fmt"
"log"
Expand Down Expand Up @@ -163,6 +164,7 @@ func resourceRedshiftUserCreate(db *DBConnection, d *schema.ResourceData) error
{userCreateDBAttr, "CREATEDB", "NOCREATEDB"},
}

userName := d.Get(userNameAttr).(string)
createOpts := make([]string, 0, len(stringOpts)+len(intOpts)+len(boolOpts))
for _, opt := range stringOpts {
v, ok := d.GetOk(opt.hclKey)
Expand All @@ -186,7 +188,7 @@ func resourceRedshiftUserCreate(db *DBConnection, d *schema.ResourceData) error
if val != "" {
switch {
case opt.hclKey == userPasswordAttr:
createOpts = append(createOpts, fmt.Sprintf("%s '%s'", opt.sqlKey, pqQuoteLiteral(val)))
createOpts = append(createOpts, fmt.Sprintf("%s '%s'", opt.sqlKey, md5Password(userName, val)))
case opt.hclKey == userValidUntilAttr:
switch {
case v.(string) == "", strings.ToLower(v.(string)) == "infinity":
Expand Down Expand Up @@ -216,7 +218,6 @@ func resourceRedshiftUserCreate(db *DBConnection, d *schema.ResourceData) error
createOpts = append(createOpts, valStr)
}

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

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

return defaultUserSyslogAccess
}

// Generates an md5 password for the user.
// Per https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_USER.html,
// the process is:
// 1. concatenate the password and username
// 2. convert the concatenated string to an md5 hash in hex format
// 3. prefix the result with 'md5' (unquoted)
func md5Password(userName string, password string) string {
return fmt.Sprintf("md5%x", md5.Sum([]byte(fmt.Sprintf("%s%s", password, userName))))
}
43 changes: 43 additions & 0 deletions redshift/resource_redshift_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package redshift
import (
"database/sql"
"fmt"
"os"
"strconv"
"strings"
"testing"

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

testAccCheckRedshiftUserExists("[email protected]"),
resource.TestCheckResourceAttr("redshift_user.with_email", "name", "[email protected]"),
testAccCheckRedshiftUserCanLogin("[email protected]", "Foobarbaz1"),

testAccCheckRedshiftUserExists("user_defaults"),
resource.TestCheckResourceAttr("redshift_user.user_with_defaults", "name", "user_defaults"),
Expand Down Expand Up @@ -284,3 +287,43 @@ func TestPermanentUsername(t *testing.T) {
t.Fatalf("permanentUsername should strip \"IAMA:\" prefix. Expected %s but was %s", expected, result)
}
}

func testAccCheckRedshiftUserCanLogin(user string, password string) resource.TestCheckFunc {
return func(s *terraform.State) error {
// there doesn't seem to be a good way to extract the provider configuration
// at runtime. However we know we've configured the provider with default settings
// so we can mimic the same behavior
port, ok := os.LookupEnv("REDSHIFT_PORT")
if !ok {
port = "5439"
}
portNum, err := strconv.Atoi(port)
if err != nil {
return fmt.Errorf("Unable to check if user can login due to bad REDSHIFT_PORT: %s", err)
}
database, ok := os.LookupEnv("REDSHIFT_DATABASE")
if !ok {
database = "redshift"
}
sslMode, ok := os.LookupEnv("REDSHIFT_SSLMODE")
if !ok {
sslMode = "require"
}
config := &Config{
Host: os.Getenv("REDSHIFT_HOST"),
Port: portNum,
Username: user,
Password: password,
Database: database,
SSLMode: sslMode,
MaxConns: defaultProviderMaxOpenConnections,
}

client, err := config.Client()
if err != nil {
return fmt.Errorf("User is unable to login %s", err)
}
defer client.Close()
return nil
}
}

0 comments on commit 004a620

Please sign in to comment.