Add HCL2 aws_secretsmanager function (#10124)

* refactor aws get secrets function out to reuse it else where
* add aws_secretsmanager func and docs for HCL2
* fix GetSecret: allow to pick secret version
This commit is contained in:
Adrien Delorme 2020-10-20 16:21:40 +02:00 committed by GitHub
parent 584fea678b
commit 6d4fae0f2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 215 additions and 116 deletions

View File

@ -9,6 +9,7 @@ import (
"sync" "sync"
consulapi "github.com/hashicorp/consul/api" consulapi "github.com/hashicorp/consul/api"
awssmapi "github.com/hashicorp/packer/template/interpolate/aws/secretsmanager"
vaultapi "github.com/hashicorp/vault/api" vaultapi "github.com/hashicorp/vault/api"
) )
@ -86,3 +87,23 @@ func Consul(k string) (string, error) {
return value, nil return value, nil
} }
func GetAWSSecret(name, key string) (string, error) {
// Check if at least 1 parameter has been used
if len(name) == 0 {
return "", errors.New("At least one secret name must be provided")
}
// client uses AWS SDK CredentialChain method. So,credentials can
// be loaded from credential file, environment variables, or IAM
// roles.
client := awssmapi.New(
&awssmapi.AWSConfig{},
)
spec := &awssmapi.SecretSpec{
Name: name,
Key: key,
}
return client.GetSecret(spec)
}

View File

@ -0,0 +1,39 @@
package function
import (
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
commontpl "github.com/hashicorp/packer/common/template"
)
// AWSSecret constructs a function that retrieves secrets from aws secrets
// manager. If Key field is not set then we will return first secret key stored
// in secret name.
var AWSSecret = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "name",
Type: cty.String,
AllowNull: false,
AllowUnknown: false,
},
{
Name: "key",
Type: cty.String,
AllowNull: true,
AllowUnknown: false,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
name := args[0].AsString()
var key string
if !args[1].IsNull() && args[1].IsWhollyKnown() {
key = args[1].AsString()
}
val, err := commontpl.GetAWSSecret(name, key)
return cty.StringVal(val), err
},
})

View File

@ -28,90 +28,91 @@ import (
func Functions(basedir string) map[string]function.Function { func Functions(basedir string) map[string]function.Function {
funcs := map[string]function.Function{ funcs := map[string]function.Function{
"abs": stdlib.AbsoluteFunc, "abs": stdlib.AbsoluteFunc,
"abspath": filesystem.AbsPathFunc, "abspath": filesystem.AbsPathFunc,
"basename": filesystem.BasenameFunc, "aws_secretsmanager": pkrfunction.AWSSecret,
"base64decode": encoding.Base64DecodeFunc, "basename": filesystem.BasenameFunc,
"base64encode": encoding.Base64EncodeFunc, "base64decode": encoding.Base64DecodeFunc,
"bcrypt": crypto.BcryptFunc, "base64encode": encoding.Base64EncodeFunc,
"can": tryfunc.CanFunc, "bcrypt": crypto.BcryptFunc,
"ceil": stdlib.CeilFunc, "can": tryfunc.CanFunc,
"chomp": stdlib.ChompFunc, "ceil": stdlib.CeilFunc,
"chunklist": stdlib.ChunklistFunc, "chomp": stdlib.ChompFunc,
"cidrhost": cidr.HostFunc, "chunklist": stdlib.ChunklistFunc,
"cidrnetmask": cidr.NetmaskFunc, "cidrhost": cidr.HostFunc,
"cidrsubnet": cidr.SubnetFunc, "cidrnetmask": cidr.NetmaskFunc,
"cidrsubnets": cidr.SubnetsFunc, "cidrsubnet": cidr.SubnetFunc,
"coalesce": collection.CoalesceFunc, "cidrsubnets": cidr.SubnetsFunc,
"coalescelist": stdlib.CoalesceListFunc, "coalesce": collection.CoalesceFunc,
"compact": stdlib.CompactFunc, "coalescelist": stdlib.CoalesceListFunc,
"concat": stdlib.ConcatFunc, "compact": stdlib.CompactFunc,
"consul_key": pkrfunction.ConsulFunc, "concat": stdlib.ConcatFunc,
"contains": stdlib.ContainsFunc, "consul_key": pkrfunction.ConsulFunc,
"convert": typeexpr.ConvertFunc, "contains": stdlib.ContainsFunc,
"csvdecode": stdlib.CSVDecodeFunc, "convert": typeexpr.ConvertFunc,
"dirname": filesystem.DirnameFunc, "csvdecode": stdlib.CSVDecodeFunc,
"distinct": stdlib.DistinctFunc, "dirname": filesystem.DirnameFunc,
"element": stdlib.ElementFunc, "distinct": stdlib.DistinctFunc,
"file": filesystem.MakeFileFunc(basedir, false), "element": stdlib.ElementFunc,
"fileexists": filesystem.MakeFileExistsFunc(basedir), "file": filesystem.MakeFileFunc(basedir, false),
"fileset": filesystem.MakeFileSetFunc(basedir), "fileexists": filesystem.MakeFileExistsFunc(basedir),
"flatten": stdlib.FlattenFunc, "fileset": filesystem.MakeFileSetFunc(basedir),
"floor": stdlib.FloorFunc, "flatten": stdlib.FlattenFunc,
"format": stdlib.FormatFunc, "floor": stdlib.FloorFunc,
"formatdate": stdlib.FormatDateFunc, "format": stdlib.FormatFunc,
"formatlist": stdlib.FormatListFunc, "formatdate": stdlib.FormatDateFunc,
"indent": stdlib.IndentFunc, "formatlist": stdlib.FormatListFunc,
"index": stdlib.IndexFunc, "indent": stdlib.IndentFunc,
"join": stdlib.JoinFunc, "index": stdlib.IndexFunc,
"jsondecode": stdlib.JSONDecodeFunc, "join": stdlib.JoinFunc,
"jsonencode": stdlib.JSONEncodeFunc, "jsondecode": stdlib.JSONDecodeFunc,
"keys": stdlib.KeysFunc, "jsonencode": stdlib.JSONEncodeFunc,
"length": stdlib.LengthFunc, "keys": stdlib.KeysFunc,
"log": stdlib.LogFunc, "length": stdlib.LengthFunc,
"lookup": stdlib.LookupFunc, "log": stdlib.LogFunc,
"lower": stdlib.LowerFunc, "lookup": stdlib.LookupFunc,
"max": stdlib.MaxFunc, "lower": stdlib.LowerFunc,
"md5": crypto.Md5Func, "max": stdlib.MaxFunc,
"merge": stdlib.MergeFunc, "md5": crypto.Md5Func,
"min": stdlib.MinFunc, "merge": stdlib.MergeFunc,
"parseint": stdlib.ParseIntFunc, "min": stdlib.MinFunc,
"pathexpand": filesystem.PathExpandFunc, "parseint": stdlib.ParseIntFunc,
"pow": stdlib.PowFunc, "pathexpand": filesystem.PathExpandFunc,
"range": stdlib.RangeFunc, "pow": stdlib.PowFunc,
"reverse": stdlib.ReverseFunc, "range": stdlib.RangeFunc,
"replace": stdlib.ReplaceFunc, "reverse": stdlib.ReverseFunc,
"regex_replace": stdlib.RegexReplaceFunc, "replace": stdlib.ReplaceFunc,
"rsadecrypt": crypto.RsaDecryptFunc, "regex_replace": stdlib.RegexReplaceFunc,
"setintersection": stdlib.SetIntersectionFunc, "rsadecrypt": crypto.RsaDecryptFunc,
"setproduct": stdlib.SetProductFunc, "setintersection": stdlib.SetIntersectionFunc,
"setunion": stdlib.SetUnionFunc, "setproduct": stdlib.SetProductFunc,
"sha1": crypto.Sha1Func, "setunion": stdlib.SetUnionFunc,
"sha256": crypto.Sha256Func, "sha1": crypto.Sha1Func,
"sha512": crypto.Sha512Func, "sha256": crypto.Sha256Func,
"signum": stdlib.SignumFunc, "sha512": crypto.Sha512Func,
"slice": stdlib.SliceFunc, "signum": stdlib.SignumFunc,
"sort": stdlib.SortFunc, "slice": stdlib.SliceFunc,
"split": stdlib.SplitFunc, "sort": stdlib.SortFunc,
"strrev": stdlib.ReverseFunc, "split": stdlib.SplitFunc,
"substr": stdlib.SubstrFunc, "strrev": stdlib.ReverseFunc,
"timestamp": pkrfunction.TimestampFunc, "substr": stdlib.SubstrFunc,
"timeadd": stdlib.TimeAddFunc, "timestamp": pkrfunction.TimestampFunc,
"title": stdlib.TitleFunc, "timeadd": stdlib.TimeAddFunc,
"trim": stdlib.TrimFunc, "title": stdlib.TitleFunc,
"trimprefix": stdlib.TrimPrefixFunc, "trim": stdlib.TrimFunc,
"trimspace": stdlib.TrimSpaceFunc, "trimprefix": stdlib.TrimPrefixFunc,
"trimsuffix": stdlib.TrimSuffixFunc, "trimspace": stdlib.TrimSpaceFunc,
"try": tryfunc.TryFunc, "trimsuffix": stdlib.TrimSuffixFunc,
"upper": stdlib.UpperFunc, "try": tryfunc.TryFunc,
"urlencode": encoding.URLEncodeFunc, "upper": stdlib.UpperFunc,
"uuidv4": uuid.V4Func, "urlencode": encoding.URLEncodeFunc,
"uuidv5": uuid.V5Func, "uuidv4": uuid.V4Func,
"values": stdlib.ValuesFunc, "uuidv5": uuid.V5Func,
"vault": pkrfunction.VaultFunc, "values": stdlib.ValuesFunc,
"yamldecode": ctyyaml.YAMLDecodeFunc, "vault": pkrfunction.VaultFunc,
"yamlencode": ctyyaml.YAMLEncodeFunc, "yamldecode": ctyyaml.YAMLDecodeFunc,
"zipmap": stdlib.ZipmapFunc, "yamlencode": ctyyaml.YAMLEncodeFunc,
"zipmap": stdlib.ZipmapFunc,
} }
return funcs return funcs

View File

@ -53,6 +53,9 @@ func (c *Client) GetSecret(spec *SecretSpec) (string, error) {
SecretId: aws.String(spec.Name), SecretId: aws.String(spec.Name),
VersionStage: aws.String("AWSCURRENT"), VersionStage: aws.String("AWSCURRENT"),
} }
if spec.Name != "" {
params.VersionStage = aws.String(spec.Key)
}
resp, err := c.api.GetSecretValue(params) resp, err := c.api.GetSecretValue(params)
if err != nil { if err != nil {

View File

@ -13,7 +13,6 @@ import (
commontpl "github.com/hashicorp/packer/common/template" commontpl "github.com/hashicorp/packer/common/template"
"github.com/hashicorp/packer/common/uuid" "github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/common"
awssmapi "github.com/hashicorp/packer/template/interpolate/aws/secretsmanager"
"github.com/hashicorp/packer/version" "github.com/hashicorp/packer/version"
strftime "github.com/jehiah/go-strftime" strftime "github.com/jehiah/go-strftime"
) )
@ -280,37 +279,16 @@ func funcGenAwsSecrets(ctx *Context) interface{} {
// semantic checks should catch this. // semantic checks should catch this.
return "", errors.New("AWS Secrets Manager is only allowed in the variables section") return "", errors.New("AWS Secrets Manager is only allowed in the variables section")
} }
switch len(secret) {
// Check if at least 1 parameter has been used case 0:
if len(secret) == 0 { return "", errors.New("secret name must be provided")
return "", errors.New("At least one secret name must be provided") case 1:
return commontpl.GetAWSSecret(secret[0], "")
case 2:
return commontpl.GetAWSSecret(secret[0], secret[1])
default:
return "", errors.New("only secret name and optional secret key can be provided.")
} }
// client uses AWS SDK CredentialChain method. So,credentials can
// be loaded from credential file, environment variables, or IAM
// roles.
client := awssmapi.New(
&awssmapi.AWSConfig{},
)
var name, key string
name = secret[0]
// key is optional if not used we fetch the first
// value stored in given secret. If more than two parameters
// are passed we take second param and ignore the others
if len(secret) > 1 {
key = secret[1]
}
spec := &awssmapi.SecretSpec{
Name: name,
Key: key,
}
s, err := client.GetSecret(spec)
if err != nil {
return "", err
}
return s, nil
} }
} }

View File

@ -32,8 +32,9 @@ export default [
{ {
category: 'contextual', category: 'contextual',
content: [ content: [
'vault', 'aws_secretsmanager',
'consul', 'consul',
'vault',
], ],
}, },
{ {

View File

@ -0,0 +1,56 @@
---
layout: docs
page_title: aws_secretsmanager - Functions - Configuration Language
sidebar_title: aws_secretsmanager
description: The aws_secretsmanager function retrieves secrets from Amazon secretsmanager stores.
---
# `aws_secretsmanager_key` Function
Secrets can be read from the [AWS Secrets
Manager](https://aws.amazon.com/secrets-manager/) and used within your template
as locals.
```hcl
aws_secretsmanager(name, key)
```
When key is not set (`null` or empty: `""`) then `aws_secretsmanager` returns
the first secret key stored in secret `name` using the `AWSCURRENT`.
You can either use this function in a `locals` block or directly inline where
you want to use the value.
```hcl
locals {
// null is equivalent to "AWSCURRENT"
current_version = aws_secretsmanager("my_secret", null)
}
source "null" "first-example" {
communicator = "none"
}
build {
name = "my-build-name"
sources = ["null.first-example"]
provisioner "shell-local" {
environment_vars = ["TESTVAR=${build.PackerRunUUID}"]
inline = ["echo current version is '${local.current_version}'",
"echo previous version is '${aws_secretsmanager("my_secret", "AWSPREVIOUS")}'."]
}
}
```
This will load the key stored at behind `my_secret` from aws secrets manager.
In order to use this function you have to configure valid AWS credentials using
one of the following methods:
- [Environment Variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html)
- [CLI Configuration Files](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)
- [Container Credentials](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html)
- [Instance Profile Credentials](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)