Skip to content
This repository has been archived by the owner on Jan 12, 2021. It is now read-only.

Commit

Permalink
Hash MySQL passwords
Browse files Browse the repository at this point in the history
Instead of storing MySQL passwords in plaintext, hash them first. This implementation is largely based on #12128.
  • Loading branch information
joshuaspence authored and apparentlymart committed Jun 28, 2017
1 parent f0c42c2 commit f52ccb2
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 18 deletions.
30 changes: 26 additions & 4 deletions mysql/resource_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,18 @@ func resourceUser() *schema.Resource {
Default: "localhost",
},

"password": &schema.Schema{
"plaintext_password": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Sensitive: true,
StateFunc: hashSum,
},
"password": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"plaintext_password"},
Sensitive: true,
Deprecated: "Please use plaintext_password instead",
},
},
}
Expand All @@ -46,7 +54,13 @@ func CreateUser(d *schema.ResourceData, meta interface{}) error {
d.Get("user").(string),
d.Get("host").(string))

password := d.Get("password").(string)
var password string
if v, ok := d.GetOk("plaintext_password"); ok {
password = v.(string)
} else {
password = d.Get("password").(string)
}

if password != "" {
stmtSQL = stmtSQL + fmt.Sprintf(" IDENTIFIED BY '%s'", password)
}
Expand All @@ -66,8 +80,16 @@ func CreateUser(d *schema.ResourceData, meta interface{}) error {
func UpdateUser(d *schema.ResourceData, meta interface{}) error {
conf := meta.(*providerConfiguration)

if d.HasChange("password") {
_, newpw := d.GetChange("password")
var newpw interface{}
if d.HasChange("plaintext_password") {
_, newpw = d.GetChange("plaintext_password")
} else if d.HasChange("password") {
_, newpw = d.GetChange("password")
} else {
newpw = nil
}

if newpw != nil {
var stmtSQL string

/* ALTER USER syntax introduced in MySQL 5.7.6 deprecates SET PASSWORD (GH-8230) */
Expand Down
60 changes: 52 additions & 8 deletions mysql/resource_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/hashicorp/terraform/terraform"
)

func TestAccUser(t *testing.T) {
func TestAccUser_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Expand All @@ -21,11 +21,39 @@ func TestAccUser(t *testing.T) {
testAccUserExists("mysql_user.test"),
resource.TestCheckResourceAttr("mysql_user.test", "user", "jdoe"),
resource.TestCheckResourceAttr("mysql_user.test", "host", "example.com"),
resource.TestCheckResourceAttr("mysql_user.test", "password", "password"),
resource.TestCheckResourceAttr("mysql_user.test", "plaintext_password", hashSum("password")),
),
},
resource.TestStep{
Config: testAccUserConfig_newPass,
Check: resource.ComposeTestCheckFunc(
testAccUserExists("mysql_user.test"),
resource.TestCheckResourceAttr("mysql_user.test", "user", "jdoe"),
resource.TestCheckResourceAttr("mysql_user.test", "host", "example.com"),
resource.TestCheckResourceAttr("mysql_user.test", "plaintext_password", hashSum("password2")),
),
},
},
})
}

func TestAccUser_deprecated(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccUserCheckDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccUserConfig_deprecated,
Check: resource.ComposeTestCheckFunc(
testAccUserExists("mysql_user.test"),
resource.TestCheckResourceAttr("mysql_user.test", "user", "jdoe"),
resource.TestCheckResourceAttr("mysql_user.test", "host", "example.com"),
resource.TestCheckResourceAttr("mysql_user.test", "password", "password"),
),
},
resource.TestStep{
Config: testAccUserConfig_deprecated_newPass,
Check: resource.ComposeTestCheckFunc(
testAccUserExists("mysql_user.test"),
resource.TestCheckResourceAttr("mysql_user.test", "user", "jdoe"),
Expand Down Expand Up @@ -86,16 +114,32 @@ func testAccUserCheckDestroy(s *terraform.State) error {

const testAccUserConfig_basic = `
resource "mysql_user" "test" {
user = "jdoe"
host = "example.com"
password = "password"
user = "jdoe"
host = "example.com"
plaintext_password = "password"
}
`

const testAccUserConfig_newPass = `
resource "mysql_user" "test" {
user = "jdoe"
host = "example.com"
password = "password2"
user = "jdoe"
host = "example.com"
plaintext_password = "password2"
}
`

const testAccUserConfig_deprecated = `
resource "mysql_user" "test" {
user = "jdoe"
host = "example.com"
password = "password"
}
`

const testAccUserConfig_deprecated_newPass = `
resource "mysql_user" "test" {
user = "jdoe"
host = "example.com"
password = "password2"
}
`
10 changes: 10 additions & 0 deletions mysql/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package mysql

import (
"crypto/sha256"
"fmt"
)

func hashSum(contents interface{}) string {
return fmt.Sprintf("%x", sha256.Sum256([]byte(contents.(string))))
}
19 changes: 13 additions & 6 deletions website/docs/r/user.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@ description: |-
The ``mysql_user`` resource creates and manages a user on a MySQL
server.

~> **Note:** All arguments including username and password will be stored in the raw state as plain-text.
~> **Note:** The password for the user is provided in plain text, and is
obscured by an unsalted hash in the state
[Read more about sensitive data in state](/docs/state/sensitive-data.html).
Care is required when using this resource, to avoid disclosing the password.

## Example Usage

```hcl
resource "mysql_user" "jdoe" {
user = "jdoe"
host = "example.com"
password = "password"
user = "jdoe"
host = "example.com"
plaintext_password = "password"
}
```

Expand All @@ -32,8 +34,13 @@ The following arguments are supported:

* `host` - (Optional) The source host of the user. Defaults to "localhost".

* `password` - (Optional) The password of the user. The value of this
argument is plain-text so make sure to secure where this is defined.
* `plaintext_password` - (Optional) The password for the user. This must be
provided in plain text, so the data source for it must be secured.
An _unsalted_ hash of the provided password is stored in state.

* `password` - (Optional) Deprecated alias of `plaintext_password`, whose
value is *stored as plaintext in state*. Prefer to use `plaintext_password`
instead, which stores the password as an unsalted hash.

## Attributes Reference

Expand Down

0 comments on commit f52ccb2

Please sign in to comment.