169 lines
5.3 KiB
Go
169 lines
5.3 KiB
Go
//go:generate struct-markdown
|
||
//go:generate mapstructure-to-hcl2 -type DatasourceOutput,Config
|
||
package secretsmanager
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"strconv"
|
||
|
||
"github.com/aws/aws-sdk-go/aws"
|
||
"github.com/aws/aws-sdk-go/service/secretsmanager"
|
||
"github.com/hashicorp/hcl/v2/hcldec"
|
||
"github.com/hashicorp/packer-plugin-sdk/hcl2helper"
|
||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||
"github.com/zclconf/go-cty/cty"
|
||
)
|
||
|
||
type Datasource struct {
|
||
config Config
|
||
}
|
||
|
||
type Config struct {
|
||
// Specifies the secret containing the version that you want to retrieve.
|
||
// You can specify either the Amazon Resource Name (ARN) or the friendly name of the secret.
|
||
Name string `mapstructure:"name" required:"true"`
|
||
// Optional key for JSON secrets that contain more than one value. When set, the `value` output will
|
||
// contain the value for the provided key.
|
||
Key string `mapstructure:"key"`
|
||
// Specifies the unique identifier of the version of the secret that you want to retrieve.
|
||
// Overrides version_stage.
|
||
VersionId string `mapstructure:"version_id"`
|
||
// Specifies the secret version that you want to retrieve by the staging label attached to the version.
|
||
// Defaults to AWSCURRENT.
|
||
VersionStage string `mapstructure:"version_stage"`
|
||
awscommon.AccessConfig `mapstructure:",squash"`
|
||
}
|
||
|
||
func (d *Datasource) ConfigSpec() hcldec.ObjectSpec {
|
||
return d.config.FlatMapstructure().HCL2Spec()
|
||
}
|
||
|
||
func (d *Datasource) Configure(raws ...interface{}) error {
|
||
err := config.Decode(&d.config, nil, raws...)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
var errs *packersdk.MultiError
|
||
errs = packersdk.MultiErrorAppend(errs, d.config.AccessConfig.Prepare()...)
|
||
|
||
if d.config.Name == "" {
|
||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("a 'name' must be provided"))
|
||
}
|
||
|
||
if d.config.VersionStage == "" {
|
||
d.config.VersionStage = "AWSCURRENT"
|
||
}
|
||
|
||
if errs != nil && len(errs.Errors) > 0 {
|
||
return errs
|
||
}
|
||
return nil
|
||
}
|
||
|
||
type DatasourceOutput struct {
|
||
// When a [key](#key) is provided, this will be the value for that key. If a key is not provided,
|
||
// `value` will contain the first value found in the secret string.
|
||
Value string `mapstructure:"value"`
|
||
// The decrypted part of the protected secret information that
|
||
// was originally provided as a string.
|
||
SecretString string `mapstructure:"secret_string"`
|
||
// The decrypted part of the protected secret information that
|
||
// was originally provided as a binary. Base64 encoded.
|
||
SecretBinary string `mapstructure:"secret_binary"`
|
||
// The unique identifier of this version of the secret.
|
||
VersionId string `mapstructure:"version_id"`
|
||
}
|
||
|
||
func (d *Datasource) OutputSpec() hcldec.ObjectSpec {
|
||
return (&DatasourceOutput{}).FlatMapstructure().HCL2Spec()
|
||
}
|
||
|
||
func (d *Datasource) Execute() (cty.Value, error) {
|
||
session, err := d.config.Session()
|
||
if err != nil {
|
||
return cty.NullVal(cty.EmptyObject), err
|
||
}
|
||
|
||
input := &secretsmanager.GetSecretValueInput{
|
||
SecretId: aws.String(d.config.Name),
|
||
}
|
||
|
||
version := ""
|
||
if d.config.VersionId != "" {
|
||
input.VersionId = aws.String(d.config.VersionId)
|
||
version = d.config.VersionId
|
||
} else {
|
||
input.VersionStage = aws.String(d.config.VersionStage)
|
||
version = d.config.VersionStage
|
||
}
|
||
|
||
secretsApi := secretsmanager.New(session)
|
||
secret, err := secretsApi.GetSecretValue(input)
|
||
if err != nil {
|
||
if awserrors.Matches(err, secretsmanager.ErrCodeResourceNotFoundException, "") {
|
||
return cty.NullVal(cty.EmptyObject), fmt.Errorf("Secrets Manager Secret %q Version %q not found", d.config.Name, version)
|
||
}
|
||
if awserrors.Matches(err, secretsmanager.ErrCodeInvalidRequestException, "You can’t perform this operation on the secret because it was deleted") {
|
||
return cty.NullVal(cty.EmptyObject), fmt.Errorf("Secrets Manager Secret %q Version %q not found", d.config.Name, version)
|
||
}
|
||
return cty.NullVal(cty.EmptyObject), fmt.Errorf("error reading Secrets Manager Secret Version: %s", err)
|
||
}
|
||
|
||
value, err := getSecretValue(aws.StringValue(secret.SecretString), d.config.Key)
|
||
if err != nil {
|
||
return cty.NullVal(cty.EmptyObject), fmt.Errorf("error to get secret value: %q", err.Error())
|
||
}
|
||
|
||
versionId := aws.StringValue(secret.VersionId)
|
||
output := DatasourceOutput{
|
||
Value: value,
|
||
SecretString: aws.StringValue(secret.SecretString),
|
||
SecretBinary: fmt.Sprintf("%s", secret.SecretBinary),
|
||
VersionId: versionId,
|
||
}
|
||
return hcl2helper.HCL2ValueFromConfig(output, d.OutputSpec()), nil
|
||
}
|
||
|
||
func getSecretValue(secretString string, key string) (string, error) {
|
||
var secretValue map[string]interface{}
|
||
blob := []byte(secretString)
|
||
|
||
//For those plaintext secrets just return the value
|
||
if json.Valid(blob) != true {
|
||
return secretString, nil
|
||
}
|
||
|
||
err := json.Unmarshal(blob, &secretValue)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
if key == "" {
|
||
for _, v := range secretValue {
|
||
return getStringSecretValue(v)
|
||
}
|
||
}
|
||
|
||
if v, ok := secretValue[key]; ok {
|
||
return getStringSecretValue(v)
|
||
}
|
||
|
||
return "", nil
|
||
}
|
||
|
||
func getStringSecretValue(v interface{}) (string, error) {
|
||
switch valueType := v.(type) {
|
||
case string:
|
||
return valueType, nil
|
||
case float64:
|
||
return strconv.FormatFloat(valueType, 'f', 0, 64), nil
|
||
default:
|
||
return "", fmt.Errorf("Unsupported secret value type: %T", valueType)
|
||
}
|
||
}
|