diff --git a/go.sum b/go.sum index 1d5aba68c..597788270 100644 --- a/go.sum +++ b/go.sum @@ -621,6 +621,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= diff --git a/hcl2template/function/vault.go b/hcl2template/function/vault.go new file mode 100644 index 000000000..09a7a75bd --- /dev/null +++ b/hcl2template/function/vault.go @@ -0,0 +1,72 @@ +package function + +import ( + "errors" + "fmt" + "os" + "strings" + + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" + + vaultapi "github.com/hashicorp/vault/api" +) + +// 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() + + if token := os.Getenv("VAULT_TOKEN"); token == "" { + return cty.StringVal(""), 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 cty.StringVal(""), fmt.Errorf("Error getting Vault client: %s", err) + } + secret, err := cli.Logical().Read(path) + if err != nil { + return cty.StringVal(""), fmt.Errorf("Error reading vault secret: %s", err) + } + if secret == nil { + return cty.StringVal(""), 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 cty.StringVal(value.(string)), nil + } + + // neither v1 nor v2 proudced a valid value + return cty.StringVal(""), fmt.Errorf("Vault data was empty at the "+ + "given path. Warnings: %s", strings.Join(secret.Warnings, "; ")) + } + + value := data.(map[string]interface{})[key].(string) + return cty.StringVal(value), nil + }, +}) + +// Vault returns a secret from a KV store in HC vault +func Vault() (cty.Value, error) { + return VaultFunc.Call([]cty.Value{}) +} diff --git a/hcl2template/functions.go b/hcl2template/functions.go index 821c525ba..eca271e9c 100644 --- a/hcl2template/functions.go +++ b/hcl2template/functions.go @@ -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, diff --git a/website/pages/docs/from-1.5/functions/vault/index.mdx b/website/pages/docs/from-1.5/functions/vault/index.mdx new file mode 100644 index 000000000..59fb78ef5 --- /dev/null +++ b/website/pages/docs/from-1.5/functions/vault/index.mdx @@ -0,0 +1,6 @@ +--- +layout: docs +page_title: vault - Functions - Configuration Language +sidebar_title: vault Functions +description: Overview of available vault functions +--- \ No newline at end of file diff --git a/website/pages/docs/from-1.5/functions/vault/vault.mdx b/website/pages/docs/from-1.5/functions/vault/vault.mdx new file mode 100644 index 000000000..a1dc92b70 --- /dev/null +++ b/website/pages/docs/from-1.5/functions/vault/vault.mdx @@ -0,0 +1,69 @@ +--- +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 +vault("/secret/data/hello" "foo") +``` + +which will assign "my_secret": "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 +vault("secrets/hello", "foo") +``` + +This example accesses the Vault path `secret/data/foo` and returns the value +stored at the key `bar`, storing it as "my_secret". + +In order for this to work, you must set the environment variables `VAULT_TOKEN` +and `VAULT_ADDR` to valid values. + +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). \ No newline at end of file