Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
2 changes: 2 additions & 0 deletions command/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
hcppackerversiondatasource "github.com/hashicorp/packer/datasource/hcp-packer-version"
httpdatasource "github.com/hashicorp/packer/datasource/http"
nulldatasource "github.com/hashicorp/packer/datasource/null"
passworddatasource "github.com/hashicorp/packer/datasource/password"
artificepostprocessor "github.com/hashicorp/packer/post-processor/artifice"
checksumpostprocessor "github.com/hashicorp/packer/post-processor/checksum"
compresspostprocessor "github.com/hashicorp/packer/post-processor/compress"
Expand Down Expand Up @@ -73,6 +74,7 @@ var Datasources = map[string]packersdk.Datasource{
"hcp-packer-version": new(hcppackerversiondatasource.Datasource),
"http": new(httpdatasource.Datasource),
"null": new(nulldatasource.Datasource),
"password": new(passworddatasource.Datasource),
}

var pluginRegexp = regexp.MustCompile("packer-(builder|post-processor|provisioner|datasource)-(.+)")
Expand Down
140 changes: 140 additions & 0 deletions datasource/password/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

//go:generate packer-sdc struct-markdown
//go:generate packer-sdc mapstructure-to-hcl2 -type DatasourceOutput,Config
package password

import (
"fmt"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/common"
"github.com/hashicorp/packer-plugin-sdk/hcl2helper"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/zclconf/go-cty/cty"
)

type Config struct {
common.PackerConfig `mapstructure:",squash"`
// The desired length of password.
Length int64 `mapstructure:"length" required:"true"`
// Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`. Default value is true
Special bool `mapstructure:"special" required:"false"`
// Include uppercase alphabet characters in the result. Default value is true
Upper bool `mapstructure:"upper" required:"false"`
// Include lowercase alphabet characters in the result. Default value is true
Lower bool `mapstructure:"lower" required:"false"`
// Include numeric characters in the result. Default value is true
Numeric bool `mapstructure:"numeric" required:"false"`
// Minimum number of numeric characters in the result. Default value is 0
MinNumeric int64 `mapstructure:"min_numeric" required:"false"`
// Minimum number of uppercase alphabet characters in the result. Default value is 0
MinUpper int64 `mapstructure:"min_upper" required:"false"`
// Minimum number of lowercase alphabet characters in the result. Default value is 0
MinLower int64 `mapstructure:"min_lower" required:"false"`
// Minimum number of special characters in the result. Default value is 0
MinSpecial int64 `mapstructure:"min_special" required:"false"`
// 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.
OverrideSpecial string `mapstructure:"override_special" required:"false"`
}

type Datasource struct {
config Config
}

type DatasourceOutput struct {
// The result of the password generation. This is the final password string.
Result string `mapstructure:"result"`
// 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
BcryptHash string `mapstructure:"bcrypt_hash"`
}

func (d *Datasource) ConfigSpec() hcldec.ObjectSpec {
return d.config.FlatMapstructure().HCL2Spec()
}

func (d *Datasource) Configure(raws ...interface{}) error {

d.config = fetchDefaultPasswordParameters()

err := config.Decode(&d.config, nil, raws...)
if err != nil {
return err
}

var errs *packersdk.MultiError

if d.config.Length < 1 ||
d.config.Length < (d.config.MinLower+d.config.MinUpper+d.config.MinNumeric+d.config.MinSpecial) {

errs = packersdk.MultiErrorAppend(
errs,
fmt.Errorf("the minimum value for length is 1 and, "+
"length must also be >= (`min_upper` + `min_lower` + `min_numeric` + `min_special`)"))
}

if errs != nil && len(errs.Errors) > 0 {
return errs
}
return nil
}

func (d *Datasource) OutputSpec() hcldec.ObjectSpec {
return (&DatasourceOutput{}).FlatMapstructure().HCL2Spec()
}

func (d *Datasource) Execute() (cty.Value, error) {
params := StringParams{
Length: d.config.Length,
Upper: d.config.Upper,
MinUpper: d.config.MinUpper,
Lower: d.config.Lower,
MinLower: d.config.MinLower,
Numeric: d.config.Numeric,
MinNumeric: d.config.MinNumeric,
Special: d.config.Special,
MinSpecial: d.config.MinSpecial,
OverrideSpecial: d.config.OverrideSpecial,
}

result, err := CreateString(params)
if err != nil {
return cty.NullVal(cty.EmptyObject), fmt.Errorf(
"while attempting to generate a random value a read error was generated: %s",
err.Error(),
)
}

hash, err := generateHash(result)
if err != nil {
return cty.NullVal(cty.EmptyObject), fmt.Errorf(
"while attempting to generate a hash from the password an error occurred: %s",
err.Error(),
)
}

output := DatasourceOutput{
Result: result,
BcryptHash: hash,
}

return hcl2helper.HCL2ValueFromConfig(output, d.OutputSpec()), nil
}

func fetchDefaultPasswordParameters() Config {
return Config{
Special: true,
Lower: true,
Upper: true,
Numeric: true,
MinLower: int64(0),
MinUpper: int64(0),
MinNumeric: int64(0),
MinSpecial: int64(0),
}
}
90 changes: 90 additions & 0 deletions datasource/password/data.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 75 additions & 0 deletions datasource/password/data_acc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package password

import (
_ "embed"
"fmt"
"os/exec"
"testing"

"github.com/hashicorp/packer-plugin-sdk/acctest"
)

//go:embed test-fixtures/basic.pkr.hcl
var testDatasourceBasic string

//go:embed test-fixtures/basic-custom-charset.pkr.hcl
var testDatasourceBasicWithCustomCharset string

//go:embed test-fixtures/invalid-length.pkr.hcl
var testDataSourceInvalidLength string

//go:embed test-fixtures/empty-charset.pkr.hcl
var testDataSourceEmptyCharset string

func TestPasswordDataSource(t *testing.T) {
tests := []struct {
Name string
Path string
Error bool
}{
{
Name: "basic_test",
Path: testDatasourceBasic,
Error: false,
},
{
Name: "basic_with_custom_charset_test",
Path: testDatasourceBasicWithCustomCharset,
Error: false,
},
{
Name: "invalid_length_test",
Path: testDataSourceInvalidLength,
Error: true,
},
{
Name: "empty_charset_test",
Path: testDataSourceEmptyCharset,
Error: true,
},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
testCase := &acctest.PluginTestCase{
Name: tt.Name,
Template: tt.Path,
Check: func(buildCommand *exec.Cmd, logfile string) error {
if buildCommand.ProcessState != nil {
if buildCommand.ProcessState.ExitCode() != 0 && !tt.Error {
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
}
if tt.Error && buildCommand.ProcessState.ExitCode() == 0 {
return fmt.Errorf("Expected Bad exit code.")
}
}
return nil
},
}
acctest.TestPlugin(t, testCase)
})
}

}
22 changes: 22 additions & 0 deletions datasource/password/test-fixtures/basic-custom-charset.pkr.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
source "null" "example" {
communicator = "none"
}

data "password" "sample" {
length = 16
override_special = "pasword"
special = true
}

build {
name = "mybuild"
sources = [
"source.null.example"
]
provisioner "shell-local" {
inline = [
"echo password: '${data.password.sample.result}'",
"echo generated hash is '${data.password.sample.bcrypt_hash}'"
]
}
}
23 changes: 23 additions & 0 deletions datasource/password/test-fixtures/basic.pkr.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
source "null" "example" {
communicator = "none"
}

data "password" "sample" {
length = 16
special = true
lower = true
upper = true
}

build {
name = "mybuild"
sources = [
"source.null.example"
]
provisioner "shell-local" {
inline = [
"echo password: '${data.password.sample.result}'",
"echo generated hash is '${data.password.sample.bcrypt_hash}'"
]
}
}
20 changes: 20 additions & 0 deletions datasource/password/test-fixtures/empty-charset.pkr.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
source "null" "example" {
communicator = "none"
}

data "password" "sample" {
length = 16
}

build {
name = "mybuild"
sources = [
"source.null.example"
]
provisioner "shell-local" {
inline = [
"echo password: '${data.password.sample.result}'",
"echo generated hash is '${data.password.sample.bcrypt_hash}'"
]
}
}
Loading