Skip to content

Commit 1c39702

Browse files
committed
Write-only version field added. Code cleanup and new test added for alias_metadata
1 parent 1f6749a commit 1c39702

6 files changed

Lines changed: 249 additions & 97 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ FEATURES:
66
* **New Resources**: `vault_alicloud_secret_backend`, `vault_alicloud_secret_backend_role`, and ephemeral resource `vault_alicloud_access_credentials` for managing AliCloud secrets engine. ([#2858](https://github.com/hashicorp/terraform-provider-vault/pull/2858))
77
* **New Resource**: `vault_plugin_runtime` for managing plugin runtimes in Vault's plugin runtimes catalog. Requires Vault 1.15 or later.([#2835](https://github.com/hashicorp/terraform-provider-vault/pull/2835/))
88

9-
* `vault_userpass_auth` : Added support for Vault Userpass Auth Method with resource for user creation, deletion along with password and policies update capabilities. Ephemeral resource for authenticating with Userpass.([#2859](https://github.com/hashicorp/terraform-provider-vault/pull/2859))
9+
* **New Resources**: `vault_userpass_auth_backend_user` for user creation, deletion, password updates, and policy updates, and ephemeral resource `vault_userpass_auth_login` for authenticating with Userpass. ([#2859](https://github.com/hashicorp/terraform-provider-vault/pull/2859))
1010

1111
IMPROVEMENTS:
1212

internal/consts/consts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,7 @@ const (
804804
FieldPasswordWO = "password_wo"
805805
FieldPasswordHashWO = "password_hash_wo"
806806
FieldPasswordWOVersion = "password_wo_version"
807+
FieldPasswordHashWOVersion = "password_hash_wo_version"
807808
FieldCredentialsWO = "credentials_wo"
808809
FieldCredentialsWOVersion = "credentials_wo_version"
809810
FieldDataJSONWO = "data_json_wo"

internal/vault/auth/userpass/user_resource.go

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strings"
1313

1414
"github.com/go-viper/mapstructure/v2"
15+
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
1516
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
1617
"github.com/hashicorp/terraform-plugin-framework/diag"
1718
"github.com/hashicorp/terraform-plugin-framework/path"
@@ -51,10 +52,12 @@ type UserpassAuthUserResource struct {
5152
type UserpassAuthUserModel struct {
5253
token.TokenModel
5354

54-
Mount types.String `tfsdk:"mount"`
55-
Username types.String `tfsdk:"username"`
56-
PasswordWO types.String `tfsdk:"password_wo"`
57-
PasswordHashWO types.String `tfsdk:"password_hash_wo"`
55+
Mount types.String `tfsdk:"mount"`
56+
Username types.String `tfsdk:"username"`
57+
PasswordWO types.String `tfsdk:"password_wo"`
58+
PasswordWOVersion types.Int64 `tfsdk:"password_wo_version"`
59+
PasswordHashWO types.String `tfsdk:"password_hash_wo"`
60+
PasswordHashWOVersion types.Int64 `tfsdk:"password_hash_wo_version"`
5861
}
5962

6063
type UserpassAuthUserAPIModel struct {
@@ -89,21 +92,41 @@ func (r *UserpassAuthUserResource) Schema(_ context.Context, _ resource.SchemaRe
8992
Sensitive: true,
9093
WriteOnly: true,
9194
Validators: []validator.String{
92-
stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName(consts.FieldPasswordHashWO)),
9395
stringvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName(consts.FieldPasswordHashWO)),
9496
},
9597
},
98+
consts.FieldPasswordWOVersion: schema.Int64Attribute{
99+
MarkdownDescription: "Version counter for the write-only `password_wo` field. " +
100+
"Since write-only values are not stored in state, Terraform cannot detect when the password changes. " +
101+
"Increment this value whenever you update `password_wo` to ensure the new password is sent to Vault.",
102+
Optional: true,
103+
Validators: []validator.Int64{
104+
int64validator.AlsoRequires(path.MatchRelative().AtParent().AtName(consts.FieldPasswordWO)),
105+
int64validator.ConflictsWith(path.MatchRelative().AtParent().AtName(consts.FieldPasswordHashWO)),
106+
int64validator.ConflictsWith(path.MatchRelative().AtParent().AtName(consts.FieldPasswordHashWOVersion)),
107+
},
108+
},
96109
consts.FieldPasswordHashWO: schema.StringAttribute{
97110
MarkdownDescription: "Pre-hashed password for this user in bcrypt format. Available in Vault 1.17 and later. Mutually exclusive with password_wo.",
98111
Optional: true,
99112
Sensitive: true,
100113
WriteOnly: true,
101114
Validators: []validator.String{
102-
stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName(consts.FieldPasswordWO)),
103115
stringvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName(consts.FieldPasswordWO)),
104116
stringvalidator.RegexMatches(bcryptHashRegexp, "must be a bcrypt hash"),
105117
},
106118
},
119+
consts.FieldPasswordHashWOVersion: schema.Int64Attribute{
120+
MarkdownDescription: "Version counter for the write-only `password_hash_wo` field. " +
121+
"Since write-only values are not stored in state, Terraform cannot detect when the password hash changes. " +
122+
"Increment this value whenever you update `password_hash_wo` to ensure the new password hash is sent to Vault.",
123+
Optional: true,
124+
Validators: []validator.Int64{
125+
int64validator.AlsoRequires(path.MatchRelative().AtParent().AtName(consts.FieldPasswordHashWO)),
126+
int64validator.ConflictsWith(path.MatchRelative().AtParent().AtName(consts.FieldPasswordWO)),
127+
int64validator.ConflictsWith(path.MatchRelative().AtParent().AtName(consts.FieldPasswordWOVersion)),
128+
},
129+
},
107130
},
108131
}
109132

@@ -117,6 +140,15 @@ func (r *UserpassAuthUserResource) Create(ctx context.Context, req resource.Crea
117140
return
118141
}
119142

143+
// Read version fields from config to ensure they're stored in state
144+
var configData UserpassAuthUserModel
145+
resp.Diagnostics.Append(req.Config.Get(ctx, &configData)...)
146+
if resp.Diagnostics.HasError() {
147+
return
148+
}
149+
data.PasswordWOVersion = configData.PasswordWOVersion
150+
data.PasswordHashWOVersion = configData.PasswordHashWOVersion
151+
120152
resp.Diagnostics.Append(r.upsertUser(ctx, &data, req.Config, errutil.VaultCreateErr)...)
121153
if resp.Diagnostics.HasError() {
122154
return
@@ -164,6 +196,15 @@ func (r *UserpassAuthUserResource) Update(ctx context.Context, req resource.Upda
164196
return
165197
}
166198

199+
// Read version fields from config to ensure they're stored in state
200+
var configData UserpassAuthUserModel
201+
resp.Diagnostics.Append(req.Config.Get(ctx, &configData)...)
202+
if resp.Diagnostics.HasError() {
203+
return
204+
}
205+
data.PasswordWOVersion = configData.PasswordWOVersion
206+
data.PasswordHashWOVersion = configData.PasswordHashWOVersion
207+
167208
resp.Diagnostics.Append(r.upsertUser(ctx, &data, req.Config, errutil.VaultUpdateErr)...)
168209
if resp.Diagnostics.HasError() {
169210
return
@@ -271,7 +312,7 @@ func (r *UserpassAuthUserResource) upsertUser(ctx context.Context, data *Userpas
271312
return diags
272313
}
273314

274-
if err := r.updatePasswordAndPoliciesEndpoints(ctx, vaultClient, data, passwordWO.ValueString()); err != nil {
315+
if err := r.updatePasswordAndPoliciesEndpoints(ctx, vaultClient, data, passwordWO.ValueString(), passwordHashWO.ValueString()); err != nil {
275316
diags.AddError(writeErr(err))
276317
return diags
277318
}
@@ -344,14 +385,21 @@ func (r *UserpassAuthUserResource) getAPIModel(ctx context.Context, data *Userpa
344385
}
345386

346387
// updatePasswordAndPoliciesEndpoints writes legacy compatibility endpoints when needed.
347-
func (r *UserpassAuthUserResource) updatePasswordAndPoliciesEndpoints(ctx context.Context, vaultClient *api.Client, data *UserpassAuthUserModel, password string) error {
388+
func (r *UserpassAuthUserResource) updatePasswordAndPoliciesEndpoints(ctx context.Context, vaultClient *api.Client, data *UserpassAuthUserModel, password, passwordHash string) error {
348389
if password != "" {
349390
_, err := vaultClient.Logical().WriteWithContext(ctx, r.userPath(data.Mount.ValueString(), data.Username.ValueString(), "password"), map[string]any{"password": password})
350391
if err != nil && !util.Is404(err) {
351392
return fmt.Errorf("failed writing user password endpoint: %w", err)
352393
}
353394
}
354395

396+
if passwordHash != "" {
397+
_, err := vaultClient.Logical().WriteWithContext(ctx, r.userPath(data.Mount.ValueString(), data.Username.ValueString(), "password"), map[string]any{"password_hash": passwordHash})
398+
if err != nil && !util.Is404(err) {
399+
return fmt.Errorf("failed writing user password_hash endpoint: %w", err)
400+
}
401+
}
402+
355403
if data.TokenPolicies.IsNull() || data.TokenPolicies.IsUnknown() {
356404
return nil
357405
}
@@ -367,7 +415,7 @@ func (r *UserpassAuthUserResource) updatePasswordAndPoliciesEndpoints(ctx contex
367415

368416
sort.Strings(policies)
369417
payload := map[string]any{
370-
"policies": strings.Join(policies, ","),
418+
"token_policies": policies,
371419
}
372420

373421
_, err := vaultClient.Logical().WriteWithContext(ctx, r.userPath(data.Mount.ValueString(), data.Username.ValueString(), "policies"), payload)
@@ -394,7 +442,7 @@ func (r *UserpassAuthUserResource) populateDataModelFromAPI(ctx context.Context,
394442
}
395443

396444
// Save the current alias_metadata value before PopulateTokenModelFromAPI
397-
// overwrites it. On Vault versions prior to 1.21, the CF auth plugin does
445+
// overwrites it. On Vault versions prior to 1.21, the Userpass auth does
398446
// not support alias_metadata: it is silently dropped on write and absent
399447
// on read. Restoring the value preserves plan/state consistency and avoids
400448
// a "provider produced inconsistent result" error.
@@ -407,8 +455,6 @@ func (r *UserpassAuthUserResource) populateDataModelFromAPI(ctx context.Context,
407455
if !r.supportsAliasMetadata() {
408456
data.TokenModel.AliasMetadata = savedAliasMetadata
409457
}
410-
//data.Mount = types.StringValue(data.Mount.ValueString())
411-
//data.Username = types.StringValue(data.Username.ValueString())
412458

413459
return nil
414460
}

0 commit comments

Comments
 (0)