Skip to content

Commit 7d4676c

Browse files
authored
Merge pull request #254 from hashicorp/add_hash_to_random_password
Add a hash to random_password with StateUpgrader
2 parents 789c0dc + ab7e42e commit 7d4676c

File tree

6 files changed

+224
-61
lines changed

6 files changed

+224
-61
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
## 3.2.0 (May 18, 2022)
2+
3+
NEW FEATURES:
4+
5+
* resource/random_password: New attribute `bcrypt_hash`, which is the hashed password ([73](https://github.com/hashicorp/terraform-provider-random/pull/73), [102](https://github.com/hashicorp/terraform-provider-random/issues/102), [254](https://github.com/hashicorp/terraform-provider-random/pull/254))
6+
7+
NOTES:
8+
9+
* Adds or updates DESIGN.md, README.md, CONTRIBUTING.md and SUPPORT.md docs ([176](https://github.com/hashicorp/terraform-provider-random/issues/176), [235](https://github.com/hashicorp/terraform-provider-random/issues/235), [242](https://github.com/hashicorp/terraform-provider-random/pull/242)).
10+
* Removes usage of deprecated fields, types and functions ([243](https://github.com/hashicorp/terraform-provider-random/issues/243), [244](https://github.com/hashicorp/terraform-provider-random/pull/244)).
11+
* Tests all minor Terraform versions ([238](https://github.com/hashicorp/terraform-provider-random/issues/238), [241](https://github.com/hashicorp/terraform-provider-random/pull/241))
12+
* Switches to linting with golangci-lint ([237](https://github.com/hashicorp/terraform-provider-random/issues/237), [240](https://github.com/hashicorp/terraform-provider-random/pull/240)).
13+
114
## 3.1.3 (April 22, 2022)
215

316
BUG FIXES:

docs/resources/password.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ resource "aws_db_instance" "example" {
5353

5454
### Read-Only
5555

56+
- `bcrypt_hash` (String, Sensitive) A bcrypt hash of the generated random string.
5657
- `id` (String) A static value used internally by Terraform, this should not be referenced in configurations.
5758
- `result` (String, Sensitive) The generated random string.
5859

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package provider
22

33
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
48
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
"golang.org/x/crypto/bcrypt"
510
)
611

712
func resourcePassword() *schema.Resource {
@@ -11,12 +16,92 @@ func resourcePassword() *schema.Resource {
1116
"data handling in the [Terraform documentation](https://www.terraform.io/docs/language/state/sensitive-data.html).\n" +
1217
"\n" +
1318
"This resource *does* use a cryptographic random number generator.",
14-
CreateContext: createStringFunc(true),
19+
CreateContext: createPassword,
1520
ReadContext: readNil,
1621
DeleteContext: RemoveResourceFromState,
17-
Schema: stringSchemaV1(true),
22+
Schema: passwordSchemaV1(),
1823
Importer: &schema.ResourceImporter{
19-
StateContext: importStringFunc(true),
24+
StateContext: importPasswordFunc,
25+
},
26+
SchemaVersion: 1,
27+
StateUpgraders: []schema.StateUpgrader{
28+
{
29+
Version: 0,
30+
Type: resourcePasswordV0().CoreConfigSchema().ImpliedType(),
31+
Upgrade: resourcePasswordStateUpgradeV0,
32+
},
2033
},
2134
}
2235
}
36+
37+
func createPassword(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
38+
diags := createStringFunc(true)(ctx, d, meta)
39+
if diags.HasError() {
40+
return diags
41+
}
42+
43+
hash, err := generateHash(d.Get("result").(string))
44+
if err != nil {
45+
diags = append(diags, diag.Errorf("err: %s", err)...)
46+
return diags
47+
}
48+
49+
if err := d.Set("bcrypt_hash", hash); err != nil {
50+
diags = append(diags, diag.Errorf("err: %s", err)...)
51+
return diags
52+
}
53+
54+
return nil
55+
}
56+
57+
func importPasswordFunc(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
58+
val := d.Id()
59+
d.SetId("none")
60+
61+
if err := d.Set("result", val); err != nil {
62+
return nil, fmt.Errorf("resource password import failed, error setting result: %w", err)
63+
}
64+
65+
hash, err := generateHash(val)
66+
if err != nil {
67+
return nil, fmt.Errorf("resource password import failed, generate hash error: %w", err)
68+
}
69+
70+
if err := d.Set("bcrypt_hash", hash); err != nil {
71+
return nil, fmt.Errorf("resource password import failed, error setting bcrypt_hash: %w", err)
72+
}
73+
74+
return []*schema.ResourceData{d}, nil
75+
}
76+
77+
func resourcePasswordV0() *schema.Resource {
78+
return &schema.Resource{
79+
Schema: passwordSchemaV0(),
80+
}
81+
}
82+
83+
func resourcePasswordStateUpgradeV0(_ context.Context, rawState map[string]interface{}, _ interface{}) (map[string]interface{}, error) {
84+
if rawState == nil {
85+
return nil, fmt.Errorf("resource password state upgrade failed, state is nil")
86+
}
87+
88+
result, ok := rawState["result"].(string)
89+
if !ok {
90+
return nil, fmt.Errorf("resource password state upgrade failed, result could not be asserted as string: %T", rawState["result"])
91+
}
92+
93+
hash, err := generateHash(result)
94+
if err != nil {
95+
return nil, fmt.Errorf("resource password state upgrade failed, generate hash error: %w", err)
96+
}
97+
98+
rawState["bcrypt_hash"] = hash
99+
100+
return rawState, nil
101+
}
102+
103+
func generateHash(toHash string) (string, error) {
104+
hash, err := bcrypt.GenerateFromPassword([]byte(toHash), bcrypt.DefaultCost)
105+
106+
return string(hash), err
107+
}

internal/provider/resource_pasword_test.go

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package provider
22

33
import (
4+
"context"
45
"fmt"
56
"regexp"
67
"testing"
78

9+
"github.com/google/go-cmp/cmp"
810
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
911
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
1012
)
@@ -15,7 +17,9 @@ func TestAccResourcePasswordBasic(t *testing.T) {
1517
ProviderFactories: testAccProviders,
1618
Steps: []resource.TestStep{
1719
{
18-
Config: testAccResourcePasswordBasic,
20+
Config: `resource "random_password" "basic" {
21+
length = 12
22+
}`,
1923
Check: resource.ComposeTestCheckFunc(
2024
testAccResourceStringCheck("random_password.basic", &customLens{
2125
customLen: 12,
@@ -41,7 +45,7 @@ func TestAccResourcePasswordBasic(t *testing.T) {
4145
},
4246
ImportState: true,
4347
ImportStateVerify: true,
44-
ImportStateVerifyIgnore: []string{"length", "lower", "number", "special", "upper", "min_lower", "min_numeric", "min_special", "min_upper", "override_special"},
48+
ImportStateVerifyIgnore: []string{"bcrypt_hash", "length", "lower", "number", "special", "upper", "min_lower", "min_numeric", "min_special", "min_upper", "override_special"},
4549
},
4650
},
4751
})
@@ -53,7 +57,13 @@ func TestAccResourcePasswordOverride(t *testing.T) {
5357
ProviderFactories: testAccProviders,
5458
Steps: []resource.TestStep{
5559
{
56-
Config: testAccResourcePasswordOverride,
60+
Config: `resource "random_password" "override" {
61+
length = 4
62+
override_special = "!"
63+
lower = false
64+
upper = false
65+
number = false
66+
}`,
5767
Check: resource.ComposeTestCheckFunc(
5868
testAccResourceStringCheck("random_password.override", &customLens{
5969
customLen: 4,
@@ -71,7 +81,14 @@ func TestAccResourcePasswordMin(t *testing.T) {
7181
ProviderFactories: testAccProviders,
7282
Steps: []resource.TestStep{
7383
{
74-
Config: testAccResourcePasswordMin,
84+
Config: `resource "random_password" "min" {
85+
length = 12
86+
override_special = "!#@"
87+
min_lower = 2
88+
min_upper = 3
89+
min_special = 1
90+
min_numeric = 4
91+
}`,
7592
Check: resource.ComposeTestCheckFunc(
7693
testAccResourceStringCheck("random_password.min", &customLens{
7794
customLen: 12,
@@ -86,29 +103,51 @@ func TestAccResourcePasswordMin(t *testing.T) {
86103
})
87104
}
88105

89-
const (
90-
testAccResourcePasswordBasic = `
91-
resource "random_password" "basic" {
92-
length = 12
93-
}`
106+
func TestResourcePasswordStateUpgradeV0(t *testing.T) {
107+
cases := []struct {
108+
name string
109+
stateV0 map[string]interface{}
110+
shouldError bool
111+
errMsg string
112+
expectedStateV1 map[string]interface{}
113+
}{
114+
{
115+
name: "result is not string",
116+
stateV0: map[string]interface{}{"result": 0},
117+
shouldError: true,
118+
errMsg: "resource password state upgrade failed, result could not be asserted as string: int",
119+
},
120+
{
121+
name: "success",
122+
stateV0: map[string]interface{}{"result": "abc123"},
123+
shouldError: false,
124+
expectedStateV1: map[string]interface{}{"result": "abc123", "bcrypt_hash": "123"},
125+
},
126+
}
94127

95-
testAccResourcePasswordOverride = `
96-
resource "random_password" "override" {
97-
length = 4
98-
override_special = "!"
99-
lower = false
100-
upper = false
101-
number = false
102-
}
103-
`
128+
for _, c := range cases {
129+
t.Run(c.name, func(t *testing.T) {
130+
actualStateV1, err := resourcePasswordStateUpgradeV0(context.Background(), c.stateV0, nil)
104131

105-
testAccResourcePasswordMin = `
106-
resource "random_password" "min" {
107-
length = 12
108-
override_special = "!#@"
109-
min_lower = 2
110-
min_upper = 3
111-
min_special = 1
112-
min_numeric = 4
113-
}`
114-
)
132+
if c.shouldError {
133+
if !cmp.Equal(c.errMsg, err.Error()) {
134+
t.Errorf("expected: %q, got: %q", c.errMsg, err)
135+
}
136+
if !cmp.Equal(c.expectedStateV1, actualStateV1) {
137+
t.Errorf("expected: %+v, got: %+v", c.expectedStateV1, err)
138+
}
139+
} else {
140+
if err != nil {
141+
t.Errorf("err should be nil, actual: %v", err)
142+
}
143+
144+
for k := range c.expectedStateV1 {
145+
_, ok := actualStateV1[k]
146+
if !ok {
147+
t.Errorf("expected key: %s is missing from state", k)
148+
}
149+
}
150+
}
151+
})
152+
}
153+
}

internal/provider/resource_string.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package provider
22

33
import (
4+
"context"
5+
"fmt"
6+
47
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
58
)
69

@@ -21,9 +24,19 @@ func resourceString() *schema.Resource {
2124
// [SDK documentation](https://github.com/hashicorp/terraform-plugin-sdk/blob/main/helper/schema/resource.go#L91).
2225
MigrateState: resourceRandomStringMigrateState,
2326
SchemaVersion: 1,
24-
Schema: stringSchemaV1(false),
27+
Schema: stringSchemaV1(),
2528
Importer: &schema.ResourceImporter{
26-
StateContext: importStringFunc(false),
29+
StateContext: importStringFunc,
2730
},
2831
}
2932
}
33+
34+
func importStringFunc(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
35+
val := d.Id()
36+
37+
if err := d.Set("result", val); err != nil {
38+
return nil, fmt.Errorf("error setting result: %w", err)
39+
}
40+
41+
return []*schema.ResourceData{d}, nil
42+
}

0 commit comments

Comments
 (0)