Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ephemeral/password: Introduce a new ephemeral password resource #625

Merged
merged 25 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7da3d6b
development branches
austinvalle Sep 4, 2024
ec164de
update pkg
austinvalle Sep 4, 2024
4a8468e
initial ephemeral password resource
austinvalle Sep 5, 2024
1188e85
comment
austinvalle Sep 5, 2024
7193c3b
Merge branch 'main' into av/ephemeral-password
austinvalle Sep 24, 2024
91d63ba
update framework branch
austinvalle Sep 24, 2024
eeedbd5
Merge branch 'main' into av/ephemeral-password
austinvalle Oct 3, 2024
71083d6
updated to latest of framework dev branch
austinvalle Oct 3, 2024
c12beb9
update to use released framework version
austinvalle Oct 31, 2024
1f9784e
generate initial docs
austinvalle Oct 31, 2024
d106747
example doc
austinvalle Oct 31, 2024
47b4ed6
Merge branch 'main' into av/ephemeral-password
austinvalle Nov 5, 2024
7e197e3
Merge branch 'main' into av/ephemeral-password
austinvalle Nov 7, 2024
5061714
Merge branch 'main' into av/ephemeral-password
austinvalle Nov 12, 2024
deff4d4
update with new testing and docs
austinvalle Nov 12, 2024
6601df3
lint error
austinvalle Nov 12, 2024
60df092
Merge branch 'main' into av/ephemeral-password
austinvalle Nov 19, 2024
2af74df
Merge branch 'main' into av/ephemeral-password
austinvalle Dec 2, 2024
7946ed7
1.10 is GA
austinvalle Dec 2, 2024
ddc0254
ignore launch.json
austinvalle Dec 2, 2024
c1c2c99
Merge branch 'main' into av/ephemeral-password
austinvalle Jan 10, 2025
2bc2101
update documentation
austinvalle Jan 10, 2025
5409c89
changelogs
austinvalle Jan 10, 2025
c9834b8
Merge branch 'main' into av/ephemeral-password
austinvalle Feb 12, 2025
fdc811d
Merge branch 'main' into av/ephemeral-password
austinvalle Feb 12, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ website/node_modules
*.iml
*.test
*.iml
.vscode

website/vendor

Expand Down
48 changes: 48 additions & 0 deletions docs/ephemeral-resources/password.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "random_password Ephemeral Resource - terraform-provider-random"
subcategory: ""
description: |-
Generates an ephemeral password string using a cryptographic random number generator.
A random ephemeral password used in combination with a write-only resource attribute will avoid Terraform storing the password string in the plan or state file.
---

# random_password (Ephemeral Resource)

Generates an ephemeral password string using a cryptographic random number generator.

A random ephemeral password used in combination with a write-only resource attribute will avoid Terraform storing the password string in the plan or state file.

## Example Usage

```terraform
ephemeral "random_password" "password" {
length = 16
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `length` (Number) The length of the string desired. The minimum value for length is 1 and, length must also be >= (`min_upper` + `min_lower` + `min_numeric` + `min_special`).

### Optional

- `lower` (Boolean) Include lowercase alphabet characters in the result. Default value is `true`.
- `min_lower` (Number) Minimum number of lowercase alphabet characters in the result. Default value is `0`.
- `min_numeric` (Number) Minimum number of numeric characters in the result. Default value is `0`.
- `min_special` (Number) Minimum number of special characters in the result. Default value is `0`.
- `min_upper` (Number) Minimum number of uppercase alphabet characters in the result. Default value is `0`.
- `numeric` (Boolean) Include numeric characters in the result. Default value is `true`. If `numeric`, `upper`, `lower`, and `special` are all configured, at least one of them must be set to `true`.
- `override_special` (String) Supply your own list of special characters to use for string generation. This overrides the default character list in the special argument. The `special` argument must still be set to true for any overwritten characters to be used in generation.
- `special` (Boolean) Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is `true`.
- `upper` (Boolean) Include uppercase alphabet characters in the result. Default value is `true`.

### Read-Only

- `bcrypt_hash` (String, Sensitive) A bcrypt hash of the generated random string. **NOTE**: If the generated random string is greater than 72 bytes in length, `bcrypt_hash` will contain a hash of the first 72 bytes.
- `result` (String, Sensitive) The generated random string.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ephemeral "random_password" "password" {
length = 16
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
}
210 changes: 210 additions & 0 deletions internal/provider/ephemeral_password.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package provider

import (
"context"

"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/terraform-providers/terraform-provider-random/internal/diagnostics"
"github.com/terraform-providers/terraform-provider-random/internal/random"
"github.com/terraform-providers/terraform-provider-random/internal/validators"
)

var (
_ ephemeral.EphemeralResource = (*passwordEphemeralResource)(nil)
)

func NewPasswordEphemeralResource() ephemeral.EphemeralResource {
return &passwordEphemeralResource{}
}

type passwordEphemeralResource struct{}

type ephemeralPasswordModel struct {
Length types.Int64 `tfsdk:"length"`
Special types.Bool `tfsdk:"special"`
Upper types.Bool `tfsdk:"upper"`
Lower types.Bool `tfsdk:"lower"`
Numeric types.Bool `tfsdk:"numeric"`
MinNumeric types.Int64 `tfsdk:"min_numeric"`
MinUpper types.Int64 `tfsdk:"min_upper"`
MinLower types.Int64 `tfsdk:"min_lower"`
MinSpecial types.Int64 `tfsdk:"min_special"`
OverrideSpecial types.String `tfsdk:"override_special"`
Result types.String `tfsdk:"result"`
BcryptHash types.String `tfsdk:"bcrypt_hash"`
}

func (e *passwordEphemeralResource) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_password"
}

func (e *passwordEphemeralResource) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) {
resp.Schema = schema.Schema{
// TODO: If we decide to release this before write-only attributes are available, we should update this phrasing
// to indicate write-only is an upcoming feature.
Description: "Generates an ephemeral password string using a cryptographic random number generator.\n" +
"\n" +
"A random ephemeral password used in combination with a write-only resource attribute will avoid Terraform storing " +
"the password string in the plan or state file.",
Attributes: map[string]schema.Attribute{
"length": schema.Int64Attribute{
Description: "The length of the string desired. The minimum value for length is 1 and, length " +
"must also be >= (`min_upper` + `min_lower` + `min_numeric` + `min_special`).",
Required: true,
Validators: []validator.Int64{
int64validator.AtLeast(1),
int64validator.AtLeastSumOf(
path.MatchRoot("min_upper"),
path.MatchRoot("min_lower"),
path.MatchRoot("min_numeric"),
path.MatchRoot("min_special"),
),
},
},
"special": schema.BoolAttribute{
Description: "Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is `true`.",
Optional: true,
Computed: true,
},
"upper": schema.BoolAttribute{
Description: "Include uppercase alphabet characters in the result. Default value is `true`.",
Optional: true,
Computed: true,
},
"lower": schema.BoolAttribute{
Description: "Include lowercase alphabet characters in the result. Default value is `true`.",
Optional: true,
Computed: true,
},
"numeric": schema.BoolAttribute{
Description: "Include numeric characters in the result. Default value is `true`. " +
"If `numeric`, `upper`, `lower`, and `special` are all configured, at least one " +
"of them must be set to `true`.",
Optional: true,
Computed: true,
Validators: []validator.Bool{
validators.AtLeastOneOfTrue(
path.MatchRoot("special"),
path.MatchRoot("upper"),
path.MatchRoot("lower"),
),
},
},
"min_numeric": schema.Int64Attribute{
Description: "Minimum number of numeric characters in the result. Default value is `0`.",
Optional: true,
Computed: true,
},

"min_upper": schema.Int64Attribute{
Description: "Minimum number of uppercase alphabet characters in the result. Default value is `0`.",
Optional: true,
Computed: true,
},
"min_lower": schema.Int64Attribute{
Description: "Minimum number of lowercase alphabet characters in the result. Default value is `0`.",
Optional: true,
Computed: true,
},
"min_special": schema.Int64Attribute{
Description: "Minimum number of special characters in the result. Default value is `0`.",
Optional: true,
Computed: true,
},
"override_special": schema.StringAttribute{
Description: "Supply your own list of special characters to use for string generation. This " +
"overrides the default character list in the special argument. The `special` argument must " +
"still be set to true for any overwritten characters to be used in generation.",
Optional: true,
},
"result": schema.StringAttribute{
Description: "The generated random string.",
Computed: true,
Sensitive: true,
},
"bcrypt_hash": schema.StringAttribute{
Description: "A bcrypt hash of the generated random string. " +
"**NOTE**: If the generated random string is greater than 72 bytes in length, " +
"`bcrypt_hash` will contain a hash of the first 72 bytes.",
Computed: true,
Sensitive: true,
},
},
}
}

func (e *passwordEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) {
var data ephemeralPasswordModel

resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

applyDefaultPasswordParameters(&data)

params := random.StringParams{
Length: data.Length.ValueInt64(),
Upper: data.Upper.ValueBool(),
MinUpper: data.MinUpper.ValueInt64(),
Lower: data.Lower.ValueBool(),
MinLower: data.MinLower.ValueInt64(),
Numeric: data.Numeric.ValueBool(),
MinNumeric: data.MinNumeric.ValueInt64(),
Special: data.Special.ValueBool(),
MinSpecial: data.MinSpecial.ValueInt64(),
OverrideSpecial: data.OverrideSpecial.ValueString(),
}

result, err := random.CreateString(params)
if err != nil {
resp.Diagnostics.Append(diagnostics.RandomReadError(err.Error())...)
return
}

hash, err := generateHash(string(result))
if err != nil {
resp.Diagnostics.Append(diagnostics.HashGenerationError(err.Error())...)
}

data.BcryptHash = types.StringValue(hash)
data.Result = types.StringValue(string(result))

resp.Diagnostics.Append(resp.Result.Set(ctx, data)...)
}

func applyDefaultPasswordParameters(data *ephemeralPasswordModel) {
if data.Special.IsNull() {
data.Special = types.BoolValue(true)
}
if data.Upper.IsNull() {
data.Upper = types.BoolValue(true)
}
if data.Lower.IsNull() {
data.Lower = types.BoolValue(true)
}
if data.Numeric.IsNull() {
data.Numeric = types.BoolValue(true)
}

if data.MinNumeric.IsNull() {
data.MinNumeric = types.Int64Value(0)
}
if data.MinUpper.IsNull() {
data.MinUpper = types.Int64Value(0)
}
if data.MinLower.IsNull() {
data.MinLower = types.Int64Value(0)
}
if data.MinSpecial.IsNull() {
data.MinSpecial = types.Int64Value(0)
}
}
Loading
Loading