From 5a56775ae572b622c8db5642429abbc6677b81e6 Mon Sep 17 00:00:00 2001 From: maze Date: Fri, 5 Sep 2025 10:47:40 +0200 Subject: [PATCH] Added hclsecret package for using secrets in HCL files --- go.mod | 9 +++++++ go.sum | 16 +++++++++++ hclsecret/example_test.go | 57 +++++++++++++++++++++++++++++++++++++++ hclsecret/hclsecret.go | 49 +++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+) create mode 100644 hclsecret/example_test.go create mode 100644 hclsecret/hclsecret.go diff --git a/go.mod b/go.mod index 4eaa091..afe909a 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,8 @@ require ( ) require ( + github.com/agext/levenshtein v1.2.1 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aws/aws-sdk-go-v2 v1.38.3 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.18.10 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 // indirect @@ -27,6 +29,7 @@ require ( github.com/danieljoos/wincred v1.2.2 // indirect github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -36,12 +39,18 @@ require ( github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/hcl v1.0.1-vault-7 // indirect + github.com/hashicorp/hcl/v2 v2.24.0 // indirect github.com/keybase/go-keychain v0.0.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/zclconf/go-cty v1.16.4 // indirect + golang.org/x/mod v0.26.0 // indirect golang.org/x/net v0.42.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.35.0 // indirect golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect + golang.org/x/tools v0.35.0 // indirect ) diff --git a/go.sum b/go.sum index d8bbde5..7e60213 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,7 @@ +github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go-v2 v1.38.3 h1:B6cV4oxnMs45fql4yRH+/Po/YU+597zgWqvDpYMturk= github.com/aws/aws-sdk-go-v2 v1.38.3/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= @@ -71,6 +75,8 @@ github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0S github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= +github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= +github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= github.com/hashicorp/vault/api v1.20.0 h1:KQMHElgudOsr+IbJgmbjHnCTxEpKs9LnozA1D3nozU4= github.com/hashicorp/vault/api v1.20.0/go.mod h1:GZ4pcjfzoOWpkJ3ijHNpEoAxKEsBJnVljyTe3jM2Sms= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= @@ -85,6 +91,8 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -98,10 +106,16 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/zclconf/go-cty v1.16.4 h1:QGXaag7/7dCzb+odlGrgr+YmYZFaOCMW6DEpS+UD1eE= +github.com/zclconf/go-cty v1.16.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= @@ -109,6 +123,8 @@ golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/hclsecret/example_test.go b/hclsecret/example_test.go new file mode 100644 index 0000000..fc0d556 --- /dev/null +++ b/hclsecret/example_test.go @@ -0,0 +1,57 @@ +package hclsecret_test + +import ( + "fmt" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsimple" + "github.com/zclconf/go-cty/cty/function" + + "git.maze.io/go/secret/hclsecret" +) + +type mockProvider struct { + out []byte + err error +} + +func (p mockProvider) GetSecret(string) ([]byte, error) { + return p.out, p.err +} + +func ExampleFunction() { + // p would be your initialized secret.Provider + p := mockProvider{[]byte("it works!"), nil} + + // Our stand-in configuration file + b := []byte(` +database { + username = "root" + password = secret("password") +} +`) + + // Our stand-in configuration struct + type databaseConfig struct { + Username string `hcl:"username"` + Password string `hcl:"password"` + } + + var config struct { + Database databaseConfig `hcl:"database,block"` + } + + // HCL evaluation context + ctx := &hcl.EvalContext{ + Functions: map[string]function.Function{ + "secret": hclsecret.Function(p), + }, + } + + if err := hclsimple.Decode("example.hcl", []byte(b), ctx, &config); err != nil { + panic(err) + } + + fmt.Println(config.Database.Password) + // Output: it works! +} diff --git a/hclsecret/hclsecret.go b/hclsecret/hclsecret.go new file mode 100644 index 0000000..ce80d85 --- /dev/null +++ b/hclsecret/hclsecret.go @@ -0,0 +1,49 @@ +// Package hclsecret contains functions for exposing secrets to HCL parsers. +package hclsecret + +import ( + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" + + "git.maze.io/go/secret" +) + +var templateSpec = &function.Spec{ + Description: "Retrieve a secret from the configured secret provider", + Params: []function.Parameter{ + { + Name: "key", + Description: "Unique key identifying the secret", + Type: cty.String, + }, + }, + Type: func(args []cty.Value) (cty.Type, error) { + return cty.String, nil + }, +} + +// Function returns a HCL function for resolving secrets. +// +// Typically one would use this in a [hcl.EvalContext]: +// +// ctx := &hcl.EvalContext{ +// Functions: map[string]function.Function{ +// "secret": Function(provider), +// } +// } +// +// This exposes a new HCL function "secret". +func Function(p secret.Provider) function.Function { + var spec = new(function.Spec) + *spec = *templateSpec + + spec.Impl = func(args []cty.Value, returnType cty.Type) (cty.Value, error) { + value, err := p.GetSecret(args[0].AsString()) + if err != nil { + return cty.StringVal(""), err + } + return cty.StringVal(string(value)), nil + } + + return function.New(spec) +}