Merge pull request #9746 from jsmcnair/f-vault-function

Add vault function for HCL2 and documentation
This commit is contained in:
Megan Marsh 2020-08-13 07:18:28 -07:00 committed by GitHub
commit 321aa090df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 161 additions and 34 deletions

View File

@ -1,8 +1,14 @@
package template
import (
"errors"
"fmt"
"log"
"os"
"strings"
"sync"
vaultapi "github.com/hashicorp/vault/api"
)
// DeprecatedTemplateFunc wraps a template func to warn users that it's
@ -17,3 +23,41 @@ func DeprecatedTemplateFunc(funcName, useInstead string, deprecated func(string)
return deprecated(in)
}
}
// Vault retrieves a secret from an HC vault KV store
func Vault(path string, key string) (string, error) {
if token := os.Getenv("VAULT_TOKEN"); token == "" {
return "", errors.New("Must set VAULT_TOKEN env var in order to use vault template function")
}
vaultConfig := vaultapi.DefaultConfig()
cli, err := vaultapi.NewClient(vaultConfig)
if err != nil {
return "", fmt.Errorf("Error getting Vault client: %s", err)
}
secret, err := cli.Logical().Read(path)
if err != nil {
return "", fmt.Errorf("Error reading vault secret: %s", err)
}
if secret == nil {
return "", errors.New("Vault Secret does not exist at the given path")
}
data, ok := secret.Data["data"]
if !ok {
// maybe ths is v1, not v2 kv store
value, ok := secret.Data[key]
if ok {
return value.(string), nil
}
// neither v1 nor v2 proudced a valid value
return "", fmt.Errorf("Vault data was empty at the given path. Warnings: %s", strings.Join(secret.Warnings, "; "))
}
if val, ok := data.(map[string]interface{})[key]; ok {
return val.(string), nil
}
return "", errors.New("Vault path does not contain the requested key")
}

1
go.sum
View File

@ -623,6 +623,7 @@ github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLE
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
github.com/zclconf/go-cty v1.4.0 h1:+q+tmgyUB94HIdH/uVTIi/+kt3pt4sHwEZAcTyLoGsQ=
github.com/zclconf/go-cty v1.4.0/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ=
github.com/zclconf/go-cty v1.5.1 h1:oALUZX+aJeEBUe2a1+uD2+UTaYfEjnKFDEMRydkGvWE=
github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8=
github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=

View File

@ -0,0 +1,31 @@
package function
import (
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
commontpl "github.com/hashicorp/packer/common/template"
)
// VaultFunc constructs a function that retrieves KV secrets from HC vault
var VaultFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "path",
Type: cty.String,
},
{
Name: "key",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
path := args[0].AsString()
key := args[1].AsString()
val, err := commontpl.Vault(path, key)
return cty.StringVal(val), err
},
})

View File

@ -106,6 +106,7 @@ func Functions(basedir string) map[string]function.Function {
"uuidv4": uuid.V4Func,
"uuidv5": uuid.V5Func,
"values": stdlib.ValuesFunc,
"vault": pkrfunction.VaultFunc,
"yamldecode": ctyyaml.YAMLDecodeFunc,
"yamlencode": ctyyaml.YAMLEncodeFunc,
"zipmap": stdlib.ZipmapFunc,

View File

@ -11,11 +11,11 @@ import (
"time"
consulapi "github.com/hashicorp/consul/api"
commontpl "github.com/hashicorp/packer/common/template"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/common"
awssmapi "github.com/hashicorp/packer/template/interpolate/aws/secretsmanager"
"github.com/hashicorp/packer/version"
vaultapi "github.com/hashicorp/vault/api"
strftime "github.com/jehiah/go-strftime"
)
@ -289,40 +289,8 @@ func funcGenVault(ctx *Context) interface{} {
// semantic checks should catch this.
return "", errors.New("Vault vars are only allowed in the variables section")
}
if token := os.Getenv("VAULT_TOKEN"); token == "" {
return "", errors.New("Must set VAULT_TOKEN env var in order to " +
"use vault template function")
}
// const EnvVaultAddress = "VAULT_ADDR"
// const EnvVaultToken = "VAULT_TOKEN"
vaultConfig := vaultapi.DefaultConfig()
cli, err := vaultapi.NewClient(vaultConfig)
if err != nil {
return "", fmt.Errorf("Error getting Vault client: %s", err)
}
secret, err := cli.Logical().Read(path)
if err != nil {
return "", fmt.Errorf("Error reading vault secret: %s", err)
}
if secret == nil {
return "", errors.New("Vault Secret does not exist at the given path")
}
data, ok := secret.Data["data"]
if !ok {
// maybe ths is v1, not v2 kv store
value, ok := secret.Data[key]
if ok {
return value.(string), nil
}
// neither v1 nor v2 proudced a valid value
return "", fmt.Errorf("Vault data was empty at the "+
"given path. Warnings: %s", strings.Join(secret.Warnings, "; "))
}
value := data.(map[string]interface{})[key].(string)
return value, nil
return commontpl.Vault(path, key)
}
}

View File

@ -0,0 +1,6 @@
---
layout: docs
page_title: vault - Functions - Configuration Language
sidebar_title: vault Functions
description: Overview of available vault functions
---

View File

@ -0,0 +1,76 @@
---
layout: docs
page_title: vault - Functions - Configuration Language
sidebar_title: vault
description: The vault function retrieves secrets from HashiCorp Vault KV stores.
---
# `vault` Function
Secrets can be read from [Vault](https://www.vaultproject.io/) and used within
your template as user variables. the `vault` function is available _only_
within the default value of a user variable, allowing you to default a user
variable to a vault secret.
An example of using a v2 kv engine:
If you store a value in vault using `vault kv put secret/hello foo=world`, you
can access it using the following:
```hcl
locals {
foo = vault("/secret/data/hello" "foo")
}
```
which will assign `local.foo` with the value "world"
An example of using a v1 kv engine:
If you store a value in vault using:
vault secrets enable -version=1 -path=secrets kv
vault kv put secrets/hello foo=world
You can access it using the following:
```hcl
locals {
foo = vault("secrets/hello", "foo")
}
```
This example accesses the Vault path `secret/foo` and returns the value
stored at the key `foo`, storing it as the local variable `local.foo`.
In order for this to work, you must set the environment variables `VAULT_TOKEN`
and `VAULT_ADDR` to valid values.
-> **NOTE:** HCL functions can be used in local variable definitions or inline
with a provisioner/post-processor. They cannot be used in global variable definitions.
The api tool we use allows for more custom configuration of the Vault client via
environment variables.
The full list of available environment variables is:
```text
"VAULT_ADDR"
"VAULT_AGENT_ADDR"
"VAULT_CACERT"
"VAULT_CAPATH"
"VAULT_CLIENT_CERT"
"VAULT_CLIENT_KEY"
"VAULT_CLIENT_TIMEOUT"
"VAULT_SKIP_VERIFY"
"VAULT_NAMESPACE"
"VAULT_TLS_SERVER_NAME"
"VAULT_WRAP_TTL"
"VAULT_MAX_RETRIES"
"VAULT_TOKEN"
"VAULT_MFA"
"VAULT_RATE_LIMIT"
```
and detailed documentation for usage of each of those variables can be found
[here](https://www.vaultproject.io/docs/commands/#environment-variables).