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

Hash MySQL passwords #9

Merged
merged 1 commit into from
Aug 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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