diff --git a/go.mod b/go.mod index f663a9340..1e21d0745 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,11 @@ module github.com/hashicorp/terraform-provider-vault -go 1.21 +go 1.22.0 + +toolchain go1.23.0 require ( - cloud.google.com/go/compute/metadata v0.2.3 + cloud.google.com/go/compute/metadata v0.5.0 cloud.google.com/go/iam v1.1.2 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 @@ -13,18 +15,23 @@ require ( github.com/coreos/pkg v0.0.0-20230601102743-20bbbf26f4d8 github.com/denisenkom/go-mssqldb v0.12.3 github.com/go-sql-driver/mysql v1.7.1 - github.com/google/uuid v1.3.1 + github.com/google/uuid v1.6.0 github.com/gosimple/slug v1.13.1 github.com/hashicorp/errwrap v1.1.0 github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637 - github.com/hashicorp/go-hclog v1.5.0 + github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/go-retryablehttp v0.7.4 + github.com/hashicorp/go-retryablehttp v0.7.7 github.com/hashicorp/go-secure-stdlib/awsutil v0.2.3 github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 - github.com/hashicorp/go-version v1.6.0 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.29.0 + github.com/hashicorp/go-version v1.7.0 + github.com/hashicorp/terraform-plugin-framework v1.13.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 + github.com/hashicorp/terraform-plugin-go v0.25.0 + github.com/hashicorp/terraform-plugin-log v0.9.0 + github.com/hashicorp/terraform-plugin-mux v0.17.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 github.com/hashicorp/vault v1.11.3 github.com/hashicorp/vault-plugin-auth-jwt v0.17.0 github.com/hashicorp/vault-plugin-auth-kerberos v0.10.1 @@ -34,16 +41,15 @@ require ( github.com/jcmturner/gokrb5/v8 v8.4.4 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.5.0 - golang.org/x/crypto v0.14.0 - golang.org/x/net v0.15.0 - golang.org/x/oauth2 v0.12.0 + golang.org/x/crypto v0.29.0 + golang.org/x/net v0.28.0 + golang.org/x/oauth2 v0.22.0 google.golang.org/api v0.144.0 google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 k8s.io/utils v0.0.0-20230726121419-3b25d923346b ) require ( - cloud.google.com/go/compute v1.23.0 // indirect cloud.google.com/go/kms v1.15.2 // indirect cloud.google.com/go/monitoring v1.16.0 // indirect github.com/Azure/azure-sdk-for-go v61.4.0+incompatible // indirect @@ -62,10 +68,10 @@ require ( github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.1.1 // indirect - github.com/Masterminds/sprig/v3 v3.2.1 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect + github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect github.com/agext/levenshtein v1.2.2 // indirect github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect @@ -75,10 +81,10 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible // indirect github.com/circonus-labs/circonusllhist v0.1.3 // indirect - github.com/cloudflare/circl v1.3.3 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/containerd v1.7.0 // indirect github.com/coreos/go-oidc/v3 v3.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -88,7 +94,7 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/fatih/color v1.14.1 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/go-jose/go-jose/v3 v3.0.0 // indirect github.com/go-ldap/ldap/v3 v3.4.4 // indirect @@ -98,9 +104,9 @@ require ( github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-metrics-stackdriver v0.2.0 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect @@ -113,7 +119,7 @@ require ( github.com/hashicorp/go-kms-wrapping v0.7.0 // indirect github.com/hashicorp/go-kms-wrapping/entropy/v2 v2.0.0 // indirect github.com/hashicorp/go-kms-wrapping/v2 v2.0.8 // indirect - github.com/hashicorp/go-plugin v1.5.1 // indirect + github.com/hashicorp/go-plugin v1.6.2 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 // indirect github.com/hashicorp/go-secure-stdlib/mlock v0.1.2 // indirect @@ -123,19 +129,17 @@ require ( github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/hashicorp/hc-install v0.6.0 // indirect + github.com/hashicorp/hc-install v0.9.0 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect - github.com/hashicorp/hcl/v2 v2.18.0 // indirect + github.com/hashicorp/hcl/v2 v2.23.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/serf v0.9.7 // indirect - github.com/hashicorp/terraform-exec v0.19.0 // indirect - github.com/hashicorp/terraform-json v0.17.1 // indirect - github.com/hashicorp/terraform-plugin-go v0.19.0 // indirect - github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect - github.com/hashicorp/terraform-registry-address v0.2.2 // indirect + github.com/hashicorp/terraform-exec v0.21.0 // indirect + github.com/hashicorp/terraform-json v0.23.0 // indirect + github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect - github.com/huandu/xstrings v1.3.2 // indirect + github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.15 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect @@ -147,7 +151,7 @@ require ( github.com/klauspost/compress v1.16.5 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/cli v1.1.5 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -182,22 +186,22 @@ require ( github.com/stretchr/testify v1.8.4 // indirect github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect - github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/zclconf/go-cty v1.14.0 // indirect + github.com/zclconf/go-cty v1.15.0 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.10.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/mod v0.21.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.7.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect - google.golang.org/grpc v1.58.2 // indirect - google.golang.org/protobuf v1.31.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect diff --git a/go.sum b/go.sum index 62c4ed280..0f61be939 100644 --- a/go.sum +++ b/go.sum @@ -177,13 +177,12 @@ cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63 cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= @@ -706,10 +705,12 @@ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3 github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/sprig/v3 v3.2.1 h1:n6EPaDyLSvCEa3frruQvAiHuNp2dhBlMSmkEr+HuzGc= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= @@ -747,15 +748,13 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEV github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs= -github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= +github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= -github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= -github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= @@ -836,7 +835,6 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bytecodealliance/wasmtime-go v0.36.0/go.mod h1:q320gUxqyI8yB+ZqRuaJOEnGkAnHh6WtJjMaT2CW4wI= github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= @@ -856,8 +854,9 @@ github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= @@ -876,8 +875,8 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -1053,8 +1052,9 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= @@ -1151,8 +1151,9 @@ github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2Vvl github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -1192,10 +1193,10 @@ github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2H github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= -github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A= -github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -1324,8 +1325,9 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= @@ -1353,8 +1355,9 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/go-containerregistry v0.13.0/go.mod h1:J9FQ+eSS4a1aC2GNZxvNpbWhgp0487v+cgiilB4FqDo= github.com/google/go-metrics-stackdriver v0.2.0 h1:rbs2sxHAPn2OtUj9JdR/Gij1YKGl0BTVD0augB+HEjE= @@ -1394,8 +1397,8 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= @@ -1477,8 +1480,9 @@ github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39 github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -1504,14 +1508,14 @@ github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-plugin v1.4.8/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-plugin v1.5.0/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= -github.com/hashicorp/go-plugin v1.5.1 h1:oGm7cWBaYIp3lJpx1RUEfLWophprE2EV/KUeqBYo+6k= -github.com/hashicorp/go-plugin v1.5.1/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= +github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= +github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= -github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= @@ -1549,21 +1553,22 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hc-install v0.6.0 h1:fDHnU7JNFNSQebVKYhHZ0va1bC6SrPQ8fpebsvNr2w4= -github.com/hashicorp/hc-install v0.6.0/go.mod h1:10I912u3nntx9Umo1VAeYPUUuehk0aRQJYpMwbX5wQA= +github.com/hashicorp/hc-install v0.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE= +github.com/hashicorp/hc-install v0.9.0/go.mod h1:+6vOP+mf3tuGgMApVYtmsnDoKWMDcFXeTxCACYZ8SFg= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= -github.com/hashicorp/hcl/v2 v2.18.0 h1:wYnG7Lt31t2zYkcquwgKo6MWXzRUDIeIVU5naZwHLl8= -github.com/hashicorp/hcl/v2 v2.18.0/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= +github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= +github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -1575,18 +1580,24 @@ github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.7 h1:hkdgbqizGQHuU5IPqYM1JdSMV8nKfpuOnZYXssk9muY= github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= -github.com/hashicorp/terraform-exec v0.19.0 h1:FpqZ6n50Tk95mItTSS9BjeOVUb4eg81SpgVtZNNtFSM= -github.com/hashicorp/terraform-exec v0.19.0/go.mod h1:tbxUpe3JKruE9Cuf65mycSIT8KiNPZ0FkuTE3H4urQg= -github.com/hashicorp/terraform-json v0.17.1 h1:eMfvh/uWggKmY7Pmb3T85u86E2EQg6EQHgyRwf3RkyA= -github.com/hashicorp/terraform-json v0.17.1/go.mod h1:Huy6zt6euxaY9knPAFKjUITn8QxUFIe9VuSzb4zn/0o= -github.com/hashicorp/terraform-plugin-go v0.19.0 h1:BuZx/6Cp+lkmiG0cOBk6Zps0Cb2tmqQpDM3iAtnhDQU= -github.com/hashicorp/terraform-plugin-go v0.19.0/go.mod h1:EhRSkEPNoylLQntYsk5KrDHTZJh9HQoumZXbOGOXmec= +github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= +github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= +github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI= +github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c= +github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw= +github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU= +github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc= +github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg= +github.com/hashicorp/terraform-plugin-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974rdTxjqEhXJjbAyks= +github.com/hashicorp/terraform-plugin-go v0.25.0/go.mod h1:+SYagMYadJP86Kvn+TGeV+ofr/R3g4/If0O5sO96MVw= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.29.0 h1:wcOKYwPI9IorAJEBLzgclh3xVolO7ZorYd6U1vnok14= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.29.0/go.mod h1:qH/34G25Ugdj5FcM95cSoXzUgIbgfhVLXCcEcYaMwq8= -github.com/hashicorp/terraform-registry-address v0.2.2 h1:lPQBg403El8PPicg/qONZJDC6YlgCVbWDtNmmZKtBno= -github.com/hashicorp/terraform-registry-address v0.2.2/go.mod h1:LtwNbCihUoUZ3RYriyS2wF/lGPB6gF9ICLRtuDk7hSo= +github.com/hashicorp/terraform-plugin-mux v0.17.0 h1:/J3vv3Ps2ISkbLPiZOLspFcIZ0v5ycUXCEQScudGCCw= +github.com/hashicorp/terraform-plugin-mux v0.17.0/go.mod h1:yWuM9U1Jg8DryNfvCp+lH70WcYv6D8aooQxxxIzFDsE= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 h1:wyKCCtn6pBBL46c1uIIBNUOWlNfYXfXpVo16iDyLp8Y= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0/go.mod h1:B0Al8NyYVr8Mp/KLwssKXG1RqnTk7FySqSn4fRuLNgw= +github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= +github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= github.com/hashicorp/vault v1.11.3 h1:KROmJz/YRIaYVpwJaWYNfHDcchtugCP8GTRz+939eT8= @@ -1616,8 +1627,9 @@ github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huaweicloud/golangsdk v0.0.0-20200304081349-45ec0797f2a4/go.mod h1:WQBcHRNX9shz3928lWEvstQJtAtYI7ks6XlgtRT9Tcw= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -1759,8 +1771,9 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= @@ -2082,8 +2095,8 @@ github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvW github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -2097,8 +2110,8 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM= -github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -2194,8 +2207,8 @@ github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1 github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= -github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= @@ -2226,8 +2239,10 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= -github.com/zclconf/go-cty v1.14.0 h1:/Xrd39K7DXbHzlisFP9c4pHao4yyf+/Ug9LEz+Y/yhc= -github.com/zclconf/go-cty v1.14.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= +github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -2376,12 +2391,12 @@ golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2441,8 +2456,8 @@ golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2531,8 +2546,9 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2564,8 +2580,8 @@ golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= -golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2582,8 +2598,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2737,8 +2753,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2753,8 +2769,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2771,8 +2787,9 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2868,8 +2885,9 @@ golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2953,8 +2971,9 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -3103,12 +3122,12 @@ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 h1:U7+wNaVuSTaUqNvK2+osJ9ejEZxbjHHk8F2b6Hpx0AE= -google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:RdyHbowztCGQySiCvQPgWQWgWhGnouTdCflKoDBt32U= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -3159,8 +3178,8 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= -google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= -google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -3180,8 +3199,9 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.2-0.20230222093303-bc1253ad3743/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/consts/consts.go b/internal/consts/consts.go index 4d00b09b9..3ac58908e 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -104,7 +104,6 @@ const ( FieldUsername = "username" FieldPassword = "password" FieldPasswordFile = "password_file" - FieldClientAuth = "client_auth" FieldAuthLoginGeneric = "auth_login" FieldAuthLoginUserpass = "auth_login_userpass" FieldAuthLoginAWS = "auth_login_aws" @@ -368,7 +367,6 @@ const ( common environment variables */ EnvVarVaultNamespaceImport = "TERRAFORM_VAULT_NAMESPACE_IMPORT" - EnvVarSkipChildToken = "TERRAFORM_VAULT_SKIP_CHILD_TOKEN" // EnvVarUsername to get the username for the userpass auth method EnvVarUsername = "TERRAFORM_VAULT_USERNAME" // EnvVarPassword to get the password for the userpass auth method diff --git a/internal/framework/base/base.go b/internal/framework/base/base.go new file mode 100644 index 000000000..fcb0e4c8a --- /dev/null +++ b/internal/framework/base/base.go @@ -0,0 +1,76 @@ +package base + +import ( + "context" + "fmt" + "os" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-provider-vault/internal/consts" + "github.com/hashicorp/terraform-provider-vault/internal/provider" +) + +type withMeta struct { + meta *provider.ProviderMeta +} + +func (w *withMeta) Meta() *provider.ProviderMeta { + return w.meta +} + +// ResourceWithConfigure is a structure to be embedded within a Resource that +// implements the ResourceWithConfigure interface. +type ResourceWithConfigure struct { + withMeta +} + +// Configure enables provider-level data or clients to be set in the +// provider-defined Resource type. +func (r *ResourceWithConfigure) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { + if v, ok := request.ProviderData.(*provider.ProviderMeta); ok { + r.meta = v + } +} + +// WithImportByID is intended to be embedded in resources which import state +// via the "id" attribute. +// +// https://developer.hashicorp.com/terraform/plugin/framework/resources/import. +// +// This will ensure the Vault namespace is written to state if it is set in the +// environment. +// https://registry.terraform.io/providers/hashicorp/vault/latest/docs#namespace-support +type WithImportByID struct{} + +func (w *WithImportByID) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root(consts.FieldID), request, response) + + ns := os.Getenv(consts.EnvVarVaultNamespaceImport) + if ns != "" { + tflog.Info( + ctx, + fmt.Sprintf("Environment variable %s set, attempting TF state import", consts.EnvVarVaultNamespaceImport), + map[string]any{consts.FieldNamespace: ns}, + ) + response.Diagnostics.Append( + response.State.SetAttribute(ctx, path.Root(consts.FieldNamespace), ns)..., + ) + } +} + +// DataSourceWithConfigure is a structure to be embedded within a DataSource +// that implements the DataSourceWithConfigure interface. +type DataSourceWithConfigure struct { + withMeta +} + +// Configure enables provider-level data or clients to be set in the +// provider-defined DataSource type. +func (d *DataSourceWithConfigure) Configure(_ context.Context, request datasource.ConfigureRequest, response *datasource.ConfigureResponse) { + if v, ok := request.ProviderData.(*provider.ProviderMeta); ok { + d.meta = v + } +} diff --git a/internal/framework/base/schema.go b/internal/framework/base/schema.go new file mode 100644 index 000000000..2b61170da --- /dev/null +++ b/internal/framework/base/schema.go @@ -0,0 +1,102 @@ +package base + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-vault/internal/consts" + "github.com/hashicorp/terraform-provider-vault/internal/framework/validators" +) + +// BaseModel describes common fields for all of the Terraform resource data models +// +// Ideally this struct would be imbedded into all Resources and DataSources. +// However, the Terraform Plugin Framework doesn't support unmarshalling nested +// structs. See https://github.com/hashicorp/terraform-plugin-framework/issues/242 +// So for now, we must duplicate all fields. +type BaseModel struct { + Namespace types.String `tfsdk:"namespace"` +} + +// BaseModelLegacy describes common fields for all of the Terraform resource +// data models that have been migrated from SDKv2 to the TF Plugin Framework. +// +// Ideally this struct would be imbedded into all Resources and DataSources. +// However, the Terraform Plugin Framework doesn't support unmarshalling nested +// structs. See https://github.com/hashicorp/terraform-plugin-framework/issues/242 +// So for now, we must duplicate all fields. +type BaseModelLegacy struct { + ID types.String `tfsdk:"id"` + Namespace types.String `tfsdk:"namespace"` +} + +// MustAddBaseSchema adds the schema fields that are required for all net new +// resources and data sources built with the TF Plugin Framework. +// +// This should be called from a resources or data source's Schema() method. +func MustAddBaseSchema(s *schema.Schema) { + mustAddSchema(s, baseSchema) +} + +// MustAddLegacyBaseSchema adds the schema fields that are required for +// resources and data sources that have been migrated from SDKv2 to the +// Terraform Plugin Framework. +// +// This should be called from a resources or data source's Schema() method. +func MustAddLegacyBaseSchema(s *schema.Schema) { + mustAddSchema(s, baseSchema) + mustAddSchema(s, legacyBaseSchema) +} + +func legacyBaseSchema() map[string]schema.Attribute { + return map[string]schema.Attribute{ + // Add an 'id' field to the base schema because + // 1. The id field was implicitly added in the SDKv2, so we must + // explicitly add it for existing resources, otherwise its a + // breaking change for practitioners. + // 2. The id field is required for acceptance testing with the SDKv2 + // package. + // See: + // https://developer.hashicorp.com/terraform/plugin/framework/acctests#no-id-found-in-attributes + // https://github.com/hashicorp/terraform-plugin-framework/issues/896 + consts.FieldID: schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + } +} + +type schemaFunc func() map[string]schema.Attribute + +func baseSchema() map[string]schema.Attribute { + return map[string]schema.Attribute{ + consts.FieldNamespace: schema.StringAttribute{ + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + MarkdownDescription: "Target namespace. (requires Enterprise)", + Validators: []validator.String{ + validators.PathValidator(), + }, + }, + } +} + +func mustAddSchema(s *schema.Schema, schemaFuncs ...schemaFunc) { + for _, f := range schemaFuncs { + for k, v := range f() { + if _, ok := s.Attributes[k]; ok { + panic(fmt.Sprintf("cannot add schema field %q, already exists in the Schema map", k)) + } + + s.Attributes[k] = v + } + } +} diff --git a/internal/framework/client/client.go b/internal/framework/client/client.go new file mode 100644 index 000000000..dd17bb9c5 --- /dev/null +++ b/internal/framework/client/client.go @@ -0,0 +1,39 @@ +package client + +import ( + "context" + "fmt" + "os" + + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-provider-vault/internal/consts" + "github.com/hashicorp/terraform-provider-vault/internal/provider" + "github.com/hashicorp/vault/api" +) + +func GetClient(ctx context.Context, meta interface{}, namespace string) (*api.Client, error) { + var p *provider.ProviderMeta + + switch v := meta.(type) { + case *provider.ProviderMeta: + p = v + default: + return nil, fmt.Errorf("meta argument must be a %T, not %T", p, meta) + } + + ns := namespace + if namespace == "" { + // in order to import namespaced resources the user must provide + // the namespace from an environment variable. + ns = os.Getenv(consts.EnvVarVaultNamespaceImport) + if ns != "" { + tflog.Debug(ctx, fmt.Sprintf("Value for %q set from environment", consts.FieldNamespace)) + } + } + + if ns != "" { + return p.GetNSClient(ns) + } + + return p.GetClient() +} diff --git a/internal/framework/model/model.go b/internal/framework/model/model.go new file mode 100644 index 000000000..21d720ccf --- /dev/null +++ b/internal/framework/model/model.go @@ -0,0 +1,27 @@ +package model + +import ( + "encoding/json" + "fmt" +) + +// ToAPIModel is helper to translate Vault response data to its respective +// Vault API data model +func ToAPIModel(data, model any) error { + jsonData, err := json.Marshal(data) + if err != nil { + return fmt.Errorf( + "An unexpected error occurred while attempting to marshal the Vault response.\n\n" + + "Error: " + err.Error(), + ) + } + + err = json.Unmarshal(jsonData, &model) + if err != nil { + return fmt.Errorf( + "An unexpected error occurred while attempting to unmarshal the data.\n\n" + + "Error: " + err.Error(), + ) + } + return nil +} diff --git a/internal/framework/validators/README.md b/internal/framework/validators/README.md new file mode 100644 index 000000000..4b0a8290a --- /dev/null +++ b/internal/framework/validators/README.md @@ -0,0 +1,3 @@ +# Terraform Plugin Framework Validators + +This package contains custom Terraform Plugin Framework [validators](https://developer.hashicorp.com/terraform/plugin/framework/validation). diff --git a/internal/framework/validators/file_exists.go b/internal/framework/validators/file_exists.go new file mode 100644 index 000000000..ea16b2b0f --- /dev/null +++ b/internal/framework/validators/file_exists.go @@ -0,0 +1,62 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "context" + "fmt" + "os" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/mitchellh/go-homedir" +) + +var _ validator.String = fileExists{} + +// fileExists validates that a given token is a valid initialization token +type fileExists struct{} + +// Description describes the validation in plain text formatting. +func (v fileExists) Description(_ context.Context) string { + return "value must be a valid path to an existing file" +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v fileExists) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateString performs the validation. +func (v fileExists) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue.ValueString() + if value == "" { + response.Diagnostics.AddError("invalid file", "value cannot be empty") + return + } + + filename, err := homedir.Expand(value) + if err != nil { + response.Diagnostics.AddError("invalid file", err.Error()) + return + } + + st, err := os.Stat(filename) + if err != nil { + response.Diagnostics.AddError("invalid file", fmt.Sprintf("failed to stat path %q, err=%s", filename, err)) + return + } + + if st.IsDir() { + response.Diagnostics.AddError("invalid file", fmt.Sprintf("path %q is not a file", filename)) + return + } +} + +func FileExistsValidator() validator.String { + return fileExists{} +} diff --git a/internal/framework/validators/file_exists_test.go b/internal/framework/validators/file_exists_test.go new file mode 100644 index 000000000..434ff03c1 --- /dev/null +++ b/internal/framework/validators/file_exists_test.go @@ -0,0 +1,68 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +const testFilePath = "./testdata/fake_account.json" + +func TestFrameworkProvider_FileExistsValidator(t *testing.T) { + cases := map[string]struct { + configValue func(t *testing.T) types.String + expectedErrorCount int + }{ + "file-is-valid": { + configValue: func(t *testing.T) types.String { + return types.StringValue(testFilePath) // Path to a test fixture + }, + }, + "non-existant-file-is-not-valid": { + configValue: func(t *testing.T) types.String { + return types.StringValue("./this/path/doesnt/exist.json") // Doesn't exist + }, + expectedErrorCount: 1, + }, + "empty-string-is-not-valid": { + configValue: func(t *testing.T) types.String { + return types.StringValue("") + }, + expectedErrorCount: 1, + }, + "unconfigured-is-valid": { + configValue: func(t *testing.T) types.String { + return types.StringNull() + }, + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + // Arrange + req := validator.StringRequest{ + ConfigValue: tc.configValue(t), + } + + resp := validator.StringResponse{ + Diagnostics: diag.Diagnostics{}, + } + + f := FileExistsValidator() + + // Act + f.ValidateString(context.Background(), req, &resp) + + // Assert + if resp.Diagnostics.ErrorsCount() != tc.expectedErrorCount { + t.Errorf("Expected %d errors, got %d", tc.expectedErrorCount, resp.Diagnostics.ErrorsCount()) + } + }) + } +} diff --git a/internal/framework/validators/gcp.go b/internal/framework/validators/gcp.go new file mode 100644 index 000000000..710d01ff4 --- /dev/null +++ b/internal/framework/validators/gcp.go @@ -0,0 +1,49 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "context" + "os" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + googleoauth "golang.org/x/oauth2/google" +) + +// Credentials Validator +var _ validator.String = credentialsValidator{} + +// credentialsValidator validates that a string Attribute's is valid JSON credentials. +type credentialsValidator struct{} + +// Description describes the validation in plain text formatting. +func (v credentialsValidator) Description(_ context.Context) string { + return "value must be a path to valid JSON credentials or valid, raw, JSON credentials" +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v credentialsValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateString performs the validation. +func (v credentialsValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue.ValueString() + + // if this is a path and we can stat it, assume it's ok + if _, err := os.Stat(value); err == nil { + return + } + if _, err := googleoauth.CredentialsFromJSON(context.Background(), []byte(value)); err != nil { + response.Diagnostics.AddError("JSON credentials are not valid", err.Error()) + } +} + +func GCPCredentialsValidator() validator.String { + return credentialsValidator{} +} diff --git a/internal/framework/validators/gcp_test.go b/internal/framework/validators/gcp_test.go new file mode 100644 index 000000000..9eee65db4 --- /dev/null +++ b/internal/framework/validators/gcp_test.go @@ -0,0 +1,79 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "context" + "io/ioutil" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +const testFakeCredentialsPath = "./testdata/fake_account.json" + +func TestFrameworkProvider_CredentialsValidator(t *testing.T) { + cases := map[string]struct { + configValue func(t *testing.T) types.String + expectedErrorCount int + }{ + "configuring credentials as a path to a credentials JSON file is valid": { + configValue: func(t *testing.T) types.String { + return types.StringValue(testFakeCredentialsPath) // Path to a test fixture + }, + }, + "configuring credentials as a path to a non-existant file is NOT valid": { + configValue: func(t *testing.T) types.String { + return types.StringValue("./this/path/doesnt/exist.json") // Doesn't exist + }, + expectedErrorCount: 1, + }, + "configuring credentials as a credentials JSON string is valid": { + configValue: func(t *testing.T) types.String { + contents, err := ioutil.ReadFile(testFakeCredentialsPath) + if err != nil { + t.Fatalf("Unexpected error: %s", err) + } + stringContents := string(contents) + return types.StringValue(stringContents) + }, + }, + "configuring credentials as an empty string is not valid": { + configValue: func(t *testing.T) types.String { + return types.StringValue("") + }, + expectedErrorCount: 1, + }, + "leaving credentials unconfigured is valid": { + configValue: func(t *testing.T) types.String { + return types.StringNull() + }, + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + // Arrange + req := validator.StringRequest{ + ConfigValue: tc.configValue(t), + } + + resp := validator.StringResponse{ + Diagnostics: diag.Diagnostics{}, + } + + cv := GCPCredentialsValidator() + + // Act + cv.ValidateString(context.Background(), req, &resp) + + // Assert + if resp.Diagnostics.ErrorsCount() != tc.expectedErrorCount { + t.Errorf("Expected %d errors, got %d", tc.expectedErrorCount, resp.Diagnostics.ErrorsCount()) + } + }) + } +} diff --git a/internal/framework/validators/kerberos.go b/internal/framework/validators/kerberos.go new file mode 100644 index 000000000..820e857ba --- /dev/null +++ b/internal/framework/validators/kerberos.go @@ -0,0 +1,62 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "context" + "encoding/base64" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/jcmturner/gokrb5/v8/spnego" +) + +var _ validator.String = krbNegToken{} + +// krbNegToken validates that a given token is a valid initialization token +type krbNegToken struct{} + +// Description describes the validation in plain text formatting. +func (v krbNegToken) Description(_ context.Context) string { + return "value must be a valid initialization token" +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v krbNegToken) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateString performs the validation. +func (v krbNegToken) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue.ValueString() + if value == "" { + response.Diagnostics.AddError("invalid token", "value cannot be empty") + return + } + + b, err := base64.StdEncoding.DecodeString(value) + if err != nil { + response.Diagnostics.AddError("invalid token", "value cannot be empty") + return + } + + isNeg, _, err := spnego.UnmarshalNegToken(b) + if err != nil { + response.Diagnostics.AddError("invalid token", fmt.Sprintf("failed to unmarshal token, err=%s", err)) + return + } + + if !isNeg { + response.Diagnostics.AddError("invalid token", "not an initialization token") + return + } +} + +func KRBNegTokenValidator() validator.String { + return krbNegToken{} +} diff --git a/internal/framework/validators/kerberos_test.go b/internal/framework/validators/kerberos_test.go new file mode 100644 index 000000000..6668b12a4 --- /dev/null +++ b/internal/framework/validators/kerberos_test.go @@ -0,0 +1,75 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "context" + "encoding/base64" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +const ( + // base64 encoded SPNEGO request token + testNegTokenInit = "oIICqjCCAqagJzAlBgkqhkiG9xIBAgIGBSsFAQUCBgkqhkiC9xIBAgIGBisGAQUCBaKCAnkEggJ1YIICcQYJKoZIhvcSAQICAQBuggJgMIICXKADAgEFoQMCAQ6iBwMFAAAAAACjggFwYYIBbDCCAWigAwIBBaENGwtURVNULkdPS1JCNaIjMCGgAwIBA6EaMBgbBEhUVFAbEGhvc3QudGVzdC5nb2tyYjWjggErMIIBJ6ADAgESoQMCAQKiggEZBIIBFdS9iQq8RW9E4uei6BEb1nZ6vwMmbfzal8Ypry7ORQpa4fFF5KTRvCyEjmamxrMdl0CyawPNvSVwv88SbpCt9fXrzp4oP/UIbaR7EpsU/Aqr1NHfnB88crgMxhTfwoeDRQsse3dJZR9DK0eqov8VjABmt1fz+wDde09j1oJ2x2Nz7N0/GcZuvEOoHld/PCY7h4NW9X6NbE7M1Ye4FTjnA5LPfnP8Eqb3xTeolKe7VWbIOsTWl1eqMgpR2NaQAXrr+VKt0Yia38Mwew5s2Mm1fPhYn75SgArLZGHCVHPUn6ob3OuLzj9h2yP5zWoJ1a3OtBHhxFRrMLMzMeVw/WvFCqQDVX519IjnWXUOoDiqtkVGZ9m2T0GkgdIwgc+gAwIBEqKBxwSBxNZ7oq5M9dkXyqsdhjYFJJMg6QSCVjZi7ZJAilQ7atXt64+TdekGCiBUkd8IL9Kl/sk9+3b0EBK7YMriDwetu3ehqlbwUh824eoQ3J+3YpArJU3XZk0LzG91HyAD5BmQrxtDMNEEd7+tY4ufC3BKyAzEdzH47I2AF2K62IhLjekK2x2+f8ew/6/Tj7Xri2VHzuMNiYcygc5jrXAEKhNHixp8K93g8iOs5i27hOLQbxBw9CZfZuBUREkzXi/MTQruW/gcWZk=" + // base64 encoded response token + testNegTokenResp = "oRQwEqADCgEAoQsGCSqGSIb3EgECAg==" +) + +func TestFrameworkProvider_KRBNegTokenValidator(t *testing.T) { + cases := map[string]struct { + configValue func(t *testing.T) types.String + expectedErrorCount int + }{ + "basic": { + configValue: func(t *testing.T) types.String { + return types.StringValue(testNegTokenInit) + }, + }, + "error-b64-decoding": { + configValue: func(t *testing.T) types.String { + return types.StringValue("Negotiation foo") + }, + expectedErrorCount: 1, + }, + "error-unmarshal": { + configValue: func(t *testing.T) types.String { + return types.StringValue(base64.StdEncoding.EncodeToString([]byte(testNegTokenInit))) + }, + expectedErrorCount: 1, + }, + "error-not-init-token": { + configValue: func(t *testing.T) types.String { + return types.StringValue(testNegTokenResp) + }, + expectedErrorCount: 1, + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + // Arrange + req := validator.StringRequest{ + ConfigValue: tc.configValue(t), + } + + resp := validator.StringResponse{ + Diagnostics: diag.Diagnostics{}, + } + + k := KRBNegTokenValidator() + + // Act + k.ValidateString(context.Background(), req, &resp) + + // Assert + if resp.Diagnostics.ErrorsCount() != tc.expectedErrorCount { + t.Errorf("Expected %d errors, got %d", tc.expectedErrorCount, resp.Diagnostics.ErrorsCount()) + } + }) + } +} diff --git a/internal/framework/validators/path.go b/internal/framework/validators/path.go new file mode 100644 index 000000000..68baa0baf --- /dev/null +++ b/internal/framework/validators/path.go @@ -0,0 +1,48 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-provider-vault/internal/provider" +) + +var _ validator.String = pathValidator{} + +// pathValidator validates that a given path is a valid Vault path format +type pathValidator struct{} + +// Description describes the validation in plain text formatting. +func (v pathValidator) Description(_ context.Context) string { + return "value must be a valid Vault path format" +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v pathValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateString performs the validation. +func (v pathValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue.ValueString() + if value == "" { + response.Diagnostics.AddError("invalid Vault path", "value cannot be empty") + return + } + + if provider.RegexpPath.MatchString(value) { + response.Diagnostics.AddError("invalid Vault path", fmt.Sprintf("value %s contains leading/trailing \"/\"", value)) + } +} + +func PathValidator() validator.String { + return pathValidator{} +} diff --git a/internal/framework/validators/path_test.go b/internal/framework/validators/path_test.go new file mode 100644 index 000000000..0cd62b404 --- /dev/null +++ b/internal/framework/validators/path_test.go @@ -0,0 +1,67 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestFrameworkProvider_PathValidator(t *testing.T) { + cases := map[string]struct { + configValue func(t *testing.T) types.String + expectedErrorCount int + }{ + "valid": { + configValue: func(t *testing.T) types.String { + return types.StringValue("foo") + }, + }, + "invalid-leading": { + configValue: func(t *testing.T) types.String { + return types.StringValue("/foo") + }, + expectedErrorCount: 1, + }, + "invalid-trailing": { + configValue: func(t *testing.T) types.String { + return types.StringValue("foo/") + }, + expectedErrorCount: 1, + }, + "invalid-both": { + configValue: func(t *testing.T) types.String { + return types.StringValue("/foo/") + }, + expectedErrorCount: 1, + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + // Arrange + req := validator.StringRequest{ + ConfigValue: tc.configValue(t), + } + + resp := validator.StringResponse{ + Diagnostics: diag.Diagnostics{}, + } + + cv := PathValidator() + + // Act + cv.ValidateString(context.Background(), req, &resp) + + // Assert + if resp.Diagnostics.ErrorsCount() != tc.expectedErrorCount { + t.Errorf("Expected %d errors, got %d: %s", tc.expectedErrorCount, resp.Diagnostics.ErrorsCount(), resp.Diagnostics.Errors()) + } + }) + } +} diff --git a/internal/framework/validators/testdata/fake_account.json b/internal/framework/validators/testdata/fake_account.json new file mode 100644 index 000000000..f3362d6d2 --- /dev/null +++ b/internal/framework/validators/testdata/fake_account.json @@ -0,0 +1,7 @@ +{ + "private_key_id": "foo", + "private_key": "bar", + "client_email": "foo@bar.com", + "client_id": "id@foo.com", + "type": "service_account" +} diff --git a/internal/framework/validators/uri.go b/internal/framework/validators/uri.go new file mode 100644 index 000000000..6b3128265 --- /dev/null +++ b/internal/framework/validators/uri.go @@ -0,0 +1,77 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.String = uriValidator{} + +// uriValidator validates that the raw url is a valid request URI, and +// optionally contains supported scheme(s). +type uriValidator struct { + schemes []string +} + +// Description describes the validation in plain text formatting. +func (v uriValidator) Description(_ context.Context) string { + return "Invalid URI" +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v uriValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateString performs the validation. +func (v uriValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue.ValueString() + if value == "" { + response.Diagnostics.AddError(v.Description(ctx), "value cannot be empty") + return + } + + u, err := url.ParseRequestURI(value) + if err != nil { + response.Diagnostics.AddError(v.Description(ctx), fmt.Sprintf("Failed to parse URL, err=%s", err)) + return + } + + if len(v.schemes) == 0 { + return + } + + for _, scheme := range v.schemes { + if scheme == u.Scheme { + return + } + } + + response.Diagnostics.AddError( + v.Description(ctx), + fmt.Sprintf( + "Unsupported scheme %q. Valid schemes are: %s", + u.Scheme, + strings.Join(v.schemes, ", "), + ), + ) +} + +// URIValidator validates that the raw url is a valid request URI, and +// optionally contains supported scheme(s). +func URIValidator(schemes []string) validator.String { + return uriValidator{ + schemes: schemes, + } +} diff --git a/internal/framework/validators/uri_test.go b/internal/framework/validators/uri_test.go new file mode 100644 index 000000000..e1a634e6e --- /dev/null +++ b/internal/framework/validators/uri_test.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestFrameworkProvider_URIValidator(t *testing.T) { + cases := map[string]struct { + configValue func(t *testing.T) types.String + schemes []string + expectedErrorCount int + }{ + "basic": { + configValue: func(t *testing.T) types.String { + return types.StringValue("http://foo.baz:8080/qux") + }, + schemes: []string{"http"}, + }, + "invalid-scheme": { + configValue: func(t *testing.T) types.String { + return types.StringValue("https://foo.baz:8080/qux") + }, + schemes: []string{"http", "tcp"}, + expectedErrorCount: 1, + }, + "invalid-url": { + configValue: func(t *testing.T) types.String { + return types.StringValue("foo.bar") + }, + schemes: []string{"http", "tcp"}, + expectedErrorCount: 1, + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + // Arrange + req := validator.StringRequest{ + ConfigValue: tc.configValue(t), + } + + resp := validator.StringResponse{ + Diagnostics: diag.Diagnostics{}, + } + + cv := URIValidator(tc.schemes) + + // Act + cv.ValidateString(context.Background(), req, &resp) + + // Assert + if resp.Diagnostics.ErrorsCount() != tc.expectedErrorCount { + t.Errorf("Expected %d errors, got %d: %s", tc.expectedErrorCount, resp.Diagnostics.ErrorsCount(), resp.Diagnostics.Errors()) + } + }) + } +} diff --git a/internal/provider/auth.go b/internal/provider/auth.go index 31e5214ce..c885df664 100644 --- a/internal/provider/auth.go +++ b/internal/provider/auth.go @@ -6,6 +6,7 @@ package provider import ( "errors" "fmt" + "os" "sync" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -18,7 +19,7 @@ import ( type ( loginSchemaFunc func(string) *schema.Schema getSchemaResource func(string) *schema.Resource - validateFunc func(data *schema.ResourceData) error + validateFunc func(data *schema.ResourceData, params map[string]interface{}) error authLoginFunc func(*schema.ResourceData) (AuthLogin, error) ) @@ -138,7 +139,7 @@ func (l *AuthLoginCommon) Init(d *schema.ResourceData, authField string, validat } for _, vf := range validators { - if err := vf(d); err != nil { + if err := vf(d, params); err != nil { return err } } @@ -270,11 +271,46 @@ func (l *AuthLoginCommon) init(d *schema.ResourceData) (string, map[string]inter return path, params, nil } -func (l *AuthLoginCommon) checkRequiredFields(d *schema.ResourceData, required ...string) error { +type authDefault struct { + field string + + // envVars will override defaultVal. + // If there are multiple entries in the slice, we use the first value we + // find that is set in the environment. + envVars []string + // defaultVal is the fallback if an env var is not set + defaultVal string +} + +type authDefaults []authDefault + +func (l *AuthLoginCommon) setDefaultFields(d *schema.ResourceData, defaults authDefaults, params map[string]interface{}) error { + for _, f := range defaults { + if _, ok := l.getOk(d, f.field); !ok { + // if field is unset in the config, check env + params[f.field] = f.defaultVal + for _, envVar := range f.envVars { + val := os.Getenv(envVar) + if val != "" { + params[f.field] = val + // found a value, no need to check other options + break + } + } + } + } + + return nil +} + +func (l *AuthLoginCommon) checkRequiredFields(d *schema.ResourceData, params map[string]interface{}, required ...string) error { var missing []string for _, f := range required { - if _, ok := l.getOk(d, f); !ok { - missing = append(missing, f) + if data, ok := l.getOk(d, f); !ok { + // if the field was unset in the config + if params[f] == data { + missing = append(missing, f) + } } } diff --git a/internal/provider/auth_aws.go b/internal/provider/auth_aws.go index d8047fd26..b148460c0 100644 --- a/internal/provider/auth_aws.go +++ b/internal/provider/auth_aws.go @@ -15,6 +15,19 @@ import ( "github.com/hashicorp/terraform-provider-vault/internal/consts" ) +const ( + envVarAWSAccessKeyID = "AWS_ACCESS_KEY_ID" + envVarAWSSecretAccessKey = "AWS_SECRET_ACCESS_KEY" + envVarAWSSessionToken = "AWS_SESSION_TOKEN" + envVarAWSProfile = "AWS_PROFILE" + envVarAWSSharedCredentialsFile = "AWS_SHARED_CREDENTIALS_FILE" + envVarAWSWebIdentityTokenFile = "AWS_WEB_IDENTITY_TOKEN_FILE" + envVarAWSRoleARN = "AWS_ROLE_ARN" + envVarAWSRoleSessionName = "AWS_ROLE_SESSION_NAME" + envVarAWSRegion = "AWS_REGION" + envVarAWSDefaultRegion = "AWS_DEFAULT_REGION" +) + func init() { field := consts.FieldAuthLoginAWS if err := globalAuthLoginRegistry.Register(field, @@ -49,39 +62,33 @@ func GetAWSLoginSchemaResource(authField string) *schema.Resource { Type: schema.TypeString, Optional: true, Description: `The AWS access key ID.`, - DefaultFunc: schema.EnvDefaultFunc("AWS_ACCESS_KEY_ID", nil), }, consts.FieldAWSSecretAccessKey: { Type: schema.TypeString, Optional: true, Description: `The AWS secret access key.`, - DefaultFunc: schema.EnvDefaultFunc("AWS_SECRET_ACCESS_KEY", nil), RequiredWith: []string{fmt.Sprintf("%s.0.%s", authField, consts.FieldAWSAccessKeyID)}, }, consts.FieldAWSSessionToken: { Type: schema.TypeString, Optional: true, Description: `The AWS session token.`, - DefaultFunc: schema.EnvDefaultFunc("AWS_SESSION_TOKEN", nil), }, consts.FieldAWSProfile: { Type: schema.TypeString, Optional: true, Description: `The name of the AWS profile.`, - DefaultFunc: schema.EnvDefaultFunc("AWS_PROFILE", nil), }, consts.FieldAWSSharedCredentialsFile: { Type: schema.TypeString, Optional: true, Description: `Path to the AWS shared credentials file.`, - DefaultFunc: schema.EnvDefaultFunc("AWS_SHARED_CREDENTIALS_FILE", nil), }, consts.FieldAWSWebIdentityTokenFile: { Type: schema.TypeString, Optional: true, Description: `Path to the file containing an OAuth 2.0 access token or OpenID ` + `Connect ID token.`, - DefaultFunc: schema.EnvDefaultFunc("AWS_WEB_IDENTITY_TOKEN_FILE", nil), }, // STS assume role fields consts.FieldAWSRoleARN: { @@ -89,26 +96,17 @@ func GetAWSLoginSchemaResource(authField string) *schema.Resource { Optional: true, Description: `The ARN of the AWS Role to assume.` + `Used during STS AssumeRole`, - DefaultFunc: schema.EnvDefaultFunc("AWS_ROLE_ARN", nil), }, consts.FieldAWSRoleSessionName: { Type: schema.TypeString, Optional: true, Description: `Specifies the name to attach to the AWS role session. ` + `Used during STS AssumeRole`, - DefaultFunc: schema.EnvDefaultFunc("AWS_ROLE_SESSION_NAME", nil), }, consts.FieldAWSRegion: { Type: schema.TypeString, Optional: true, Description: `The AWS region.`, - DefaultFunc: schema.MultiEnvDefaultFunc( - []string{ - "AWS_REGION", - "AWS_DEFAULT_REGION", - }, - nil, - ), }, consts.FieldAWSSTSEndpoint: { Type: schema.TypeString, @@ -140,9 +138,13 @@ type AuthLoginAWS struct { } func (l *AuthLoginAWS) Init(d *schema.ResourceData, authField string) (AuthLogin, error) { + defaults := l.getDefaults() if err := l.AuthLoginCommon.Init(d, authField, - func(data *schema.ResourceData) error { - return l.checkRequiredFields(d, consts.FieldRole) + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.setDefaultFields(d, defaults, params) + }, + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.checkRequiredFields(d, params, consts.FieldRole) }, ); err != nil { return nil, err @@ -193,6 +195,58 @@ func (l *AuthLoginAWS) Login(client *api.Client) (*api.Secret, error) { return l.login(client, l.LoginPath(), params) } +func (l *AuthLoginAWS) getDefaults() authDefaults { + defaults := authDefaults{ + { + field: consts.FieldAWSAccessKeyID, + envVars: []string{envVarAWSAccessKeyID}, + defaultVal: "", + }, + { + field: consts.FieldAWSSecretAccessKey, + envVars: []string{envVarAWSSecretAccessKey}, + defaultVal: "", + }, + { + field: consts.FieldAWSSessionToken, + envVars: []string{envVarAWSSessionToken}, + defaultVal: "", + }, + { + field: consts.FieldAWSProfile, + envVars: []string{envVarAWSProfile}, + defaultVal: "", + }, + { + field: consts.FieldAWSSharedCredentialsFile, + envVars: []string{envVarAWSSharedCredentialsFile}, + defaultVal: "", + }, + { + field: consts.FieldAWSWebIdentityTokenFile, + envVars: []string{envVarAWSWebIdentityTokenFile}, + defaultVal: "", + }, + { + field: consts.FieldAWSRoleARN, + envVars: []string{envVarAWSRoleARN}, + defaultVal: "", + }, + { + field: consts.FieldAWSRoleSessionName, + envVars: []string{envVarAWSRoleSessionName}, + defaultVal: "", + }, + { + field: consts.FieldAWSRegion, + envVars: []string{envVarAWSRegion, envVarAWSDefaultRegion}, + defaultVal: "", + }, + } + + return defaults +} + func (l *AuthLoginAWS) getLoginData(logger hclog.Logger) (map[string]interface{}, error) { config, err := l.getCredentialsConfig(logger) if err != nil { diff --git a/internal/provider/auth_azure.go b/internal/provider/auth_azure.go index ba99d37b7..aa867b590 100644 --- a/internal/provider/auth_azure.go +++ b/internal/provider/auth_azure.go @@ -46,7 +46,6 @@ func GetAzureLoginSchemaResource(authField string) *schema.Resource { Optional: true, Description: "A signed JSON Web Token. If not specified on will be " + "created automatically", - DefaultFunc: schema.EnvDefaultFunc(consts.EnvVarAzureAuthJWT, nil), }, consts.FieldRole: { Type: schema.TypeString, @@ -94,7 +93,6 @@ func GetAzureLoginSchemaResource(authField string) *schema.Resource { consts.FieldScope: { Type: schema.TypeString, Optional: true, - Default: defaultAzureScope, Description: "The scopes to include in the token request.", ConflictsWith: []string{fmt.Sprintf("%s.0.%s", authField, consts.FieldJWT)}, }, @@ -122,12 +120,30 @@ func (l *AuthLoginAzure) LoginPath() string { } func (l *AuthLoginAzure) Init(d *schema.ResourceData, authField string) (AuthLogin, error) { + defaults := authDefaults{ + { + field: consts.FieldJWT, + envVars: []string{consts.EnvVarAzureAuthJWT}, + defaultVal: "", + }, + { + field: consts.FieldScope, + envVars: []string{""}, + defaultVal: defaultAzureScope, + }, + } if err := l.AuthLoginCommon.Init(d, authField, - func(data *schema.ResourceData) error { - err := l.checkRequiredFields(d, l.requiredParams()...) + func(data *schema.ResourceData, params map[string]interface{}) error { + err := l.setDefaultFields(d, defaults, params) + if err != nil { + return err + } + + err = l.checkRequiredFields(d, params, l.requiredParams()...) if err != nil { return err } + return l.checkFieldsOneOf(d, consts.FieldVMName, consts.FieldVMSSName) }, ); err != nil { diff --git a/internal/provider/auth_cert.go b/internal/provider/auth_cert.go index e1332e44c..cfe0bb972 100644 --- a/internal/provider/auth_cert.go +++ b/internal/provider/auth_cert.go @@ -79,8 +79,8 @@ func (l *AuthLoginCert) LoginPath() string { func (l *AuthLoginCert) Init(d *schema.ResourceData, authField string) (AuthLogin, error) { if err := l.AuthLoginCommon.Init(d, authField, - func(data *schema.ResourceData) error { - return l.checkRequiredFields(d, consts.FieldCertFile, consts.FieldKeyFile) + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.checkRequiredFields(d, params, consts.FieldCertFile, consts.FieldKeyFile) }, ); err != nil { return nil, err diff --git a/internal/provider/auth_gcp.go b/internal/provider/auth_gcp.go index 716bf17c2..397ce6532 100644 --- a/internal/provider/auth_gcp.go +++ b/internal/provider/auth_gcp.go @@ -58,7 +58,6 @@ func GetGCPLoginSchemaResource(authField string) *schema.Resource { Type: schema.TypeString, Optional: true, Description: "A signed JSON Web Token.", - DefaultFunc: schema.EnvDefaultFunc(consts.EnvVarGCPAuthJWT, nil), ConflictsWith: []string{fmt.Sprintf("%s.0.%s", authField, consts.FieldCredentials)}, }, consts.FieldCredentials: { @@ -66,7 +65,6 @@ func GetGCPLoginSchemaResource(authField string) *schema.Resource { Optional: true, ValidateFunc: validateCredentials, Description: "Path to the Google Cloud credentials file.", - DefaultFunc: schema.EnvDefaultFunc(consts.EnvVarGoogleApplicationCreds, nil), ConflictsWith: []string{fmt.Sprintf("%s.0.%s", authField, consts.FieldJWT)}, }, consts.FieldServiceAccount: { @@ -89,7 +87,26 @@ type AuthLoginGCP struct { } func (l *AuthLoginGCP) Init(d *schema.ResourceData, authField string) (AuthLogin, error) { - if err := l.AuthLoginCommon.Init(d, authField); err != nil { + defaults := authDefaults{ + { + field: consts.FieldJWT, + envVars: []string{consts.EnvVarGCPAuthJWT}, + defaultVal: "", + }, + { + field: consts.FieldCredentials, + envVars: []string{consts.EnvVarGoogleApplicationCreds}, + defaultVal: "", + }, + } + if err := l.AuthLoginCommon.Init(d, authField, + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.setDefaultFields(d, defaults, params) + }, + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.checkRequiredFields(d, params, consts.FieldRole) + }, + ); err != nil { return nil, err } return l, nil diff --git a/internal/provider/auth_jwt.go b/internal/provider/auth_jwt.go index bfa74aab5..2e080df62 100644 --- a/internal/provider/auth_jwt.go +++ b/internal/provider/auth_jwt.go @@ -42,10 +42,10 @@ func GetJWTLoginSchemaResource(authField string) *schema.Resource { Description: "Name of the login role.", }, consts.FieldJWT: { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + // can be set via an env var + Optional: true, Description: "A signed JSON Web Token.", - DefaultFunc: schema.EnvDefaultFunc(consts.EnvVarVaultAuthJWT, nil), }, }, }, authField, consts.MountTypeJWT) @@ -71,9 +71,20 @@ func (l *AuthLoginJWT) LoginPath() string { } func (l *AuthLoginJWT) Init(d *schema.ResourceData, authField string) (AuthLogin, error) { + defaults := authDefaults{ + { + field: consts.FieldJWT, + envVars: []string{consts.EnvVarVaultAuthJWT}, + defaultVal: "", + }, + } + if err := l.AuthLoginCommon.Init(d, authField, - func(data *schema.ResourceData) error { - return l.checkRequiredFields(d, consts.FieldRole, consts.FieldJWT) + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.setDefaultFields(d, defaults, params) + }, + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.checkRequiredFields(d, params, consts.FieldRole, consts.FieldJWT) }, ); err != nil { return nil, err diff --git a/internal/provider/auth_jwt_test.go b/internal/provider/auth_jwt_test.go index 7a4c302c3..af5a5fd8d 100644 --- a/internal/provider/auth_jwt_test.go +++ b/internal/provider/auth_jwt_test.go @@ -39,6 +39,29 @@ func TestAuthLoginJWT_Init(t *testing.T) { }, wantErr: false, }, + { + name: "basic-with-env", + authField: consts.FieldAuthLoginJWT, + raw: map[string]interface{}{ + consts.FieldAuthLoginJWT: []interface{}{ + map[string]interface{}{ + consts.FieldNamespace: "ns1", + consts.FieldRole: "alice", + }, + }, + }, + envVars: map[string]string{ + consts.EnvVarVaultAuthJWT: "jwt1", + }, + expectParams: map[string]interface{}{ + consts.FieldNamespace: "ns1", + consts.FieldUseRootNamespace: false, + consts.FieldMount: consts.MountTypeJWT, + consts.FieldRole: "alice", + consts.FieldJWT: "jwt1", + }, + wantErr: false, + }, { name: "error-missing-resource", authField: consts.FieldAuthLoginJWT, diff --git a/internal/provider/auth_kerberos.go b/internal/provider/auth_kerberos.go index 9c57bd909..a0851906a 100644 --- a/internal/provider/auth_kerberos.go +++ b/internal/provider/auth_kerberos.go @@ -45,7 +45,6 @@ func GetKerberosLoginSchemaResource(authField string) *schema.Resource { consts.FieldToken: { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc(consts.EnvVarKrbSPNEGOToken, nil), Description: "Simple and Protected GSSAPI Negotiation Mechanism (SPNEGO) token", ValidateFunc: validateKRBNegToken, }, @@ -71,7 +70,6 @@ func GetKerberosLoginSchemaResource(authField string) *schema.Resource { Type: schema.TypeString, Optional: true, Description: "A valid Kerberos configuration file e.g. /etc/krb5.conf.", - DefaultFunc: schema.EnvDefaultFunc(consts.EnvVarKRB5Conf, nil), ValidateFunc: validateFileExists, ConflictsWith: conflicts, }, @@ -79,21 +77,18 @@ func GetKerberosLoginSchemaResource(authField string) *schema.Resource { Type: schema.TypeString, Optional: true, Description: "The Kerberos keytab file containing the entry of the login entity.", - DefaultFunc: schema.EnvDefaultFunc(consts.EnvVarKRBKeytab, nil), ValidateFunc: validateFileExists, ConflictsWith: conflicts, }, consts.FieldDisableFastNegotiation: { Type: schema.TypeBool, Optional: true, - Default: false, ConflictsWith: conflicts, Description: "Disable the Kerberos FAST negotiation.", }, consts.FieldRemoveInstanceName: { Type: schema.TypeBool, Optional: true, - Default: false, ConflictsWith: conflicts, Description: "Strip the host from the username found in the keytab.", }, @@ -125,16 +120,31 @@ func (l *AuthLoginKerberos) LoginPath() string { } func (l *AuthLoginKerberos) Init(d *schema.ResourceData, authField string) (AuthLogin, error) { + defaults := authDefaults{ + { + field: consts.FieldToken, + envVars: []string{consts.EnvVarKrbSPNEGOToken}, + defaultVal: "", + }, + { + field: consts.FieldKRB5ConfPath, + envVars: []string{consts.EnvVarKRB5Conf}, + defaultVal: "", + }, + { + field: consts.FieldKeytabPath, + envVars: []string{consts.EnvVarKRBKeytab}, + defaultVal: "", + }, + } + if err := l.AuthLoginCommon.Init(d, authField, - func(data *schema.ResourceData) error { + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.setDefaultFields(d, defaults, params) + }, + func(data *schema.ResourceData, params map[string]interface{}) error { if _, ok := l.getOk(d, consts.FieldToken); !ok { - return l.checkRequiredFields(d, - consts.FieldUsername, - consts.FieldService, - consts.FieldRealm, - consts.FieldKeytabPath, - consts.FieldKRB5ConfPath, - ) + return l.checkRequiredFields(d, params, consts.FieldUsername, consts.FieldService, consts.FieldRealm, consts.FieldKeytabPath, consts.FieldKRB5ConfPath) } return nil }, diff --git a/internal/provider/auth_oci.go b/internal/provider/auth_oci.go index eb61faa93..057a61715 100644 --- a/internal/provider/auth_oci.go +++ b/internal/provider/auth_oci.go @@ -85,8 +85,8 @@ func (l *AuthLoginOCI) LoginPath() string { func (l *AuthLoginOCI) Init(d *schema.ResourceData, authField string) (AuthLogin, error) { if err := l.AuthLoginCommon.Init(d, authField, - func(data *schema.ResourceData) error { - return l.checkRequiredFields(d, consts.FieldRole, consts.FieldAuthType) + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.checkRequiredFields(d, params, consts.FieldRole, consts.FieldAuthType) }, ); err != nil { return nil, err diff --git a/internal/provider/auth_oidc.go b/internal/provider/auth_oidc.go index d7df69b5a..d92f6c469 100644 --- a/internal/provider/auth_oidc.go +++ b/internal/provider/auth_oidc.go @@ -86,8 +86,8 @@ func (l *AuthLoginOIDC) LoginPath() string { func (l *AuthLoginOIDC) Init(d *schema.ResourceData, authField string) (AuthLogin, error) { if err := l.AuthLoginCommon.Init(d, authField, - func(data *schema.ResourceData) error { - return l.checkRequiredFields(d, consts.FieldRole) + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.checkRequiredFields(d, params, consts.FieldRole) }, ); err != nil { return nil, err diff --git a/internal/provider/auth_radius.go b/internal/provider/auth_radius.go index 3b6b514e0..bf4effa6f 100644 --- a/internal/provider/auth_radius.go +++ b/internal/provider/auth_radius.go @@ -39,14 +39,14 @@ func GetRadiusLoginSchemaResource(authField string) *schema.Resource { consts.FieldUsername: { Type: schema.TypeString, Description: "The Radius username.", - Required: true, - DefaultFunc: schema.EnvDefaultFunc(consts.EnvVarRadiusUsername, nil), + // can be set via an env var + Optional: true, }, consts.FieldPassword: { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + // can be set via an env var + Optional: true, Description: "The Radius password for username.", - DefaultFunc: schema.EnvDefaultFunc(consts.EnvVarRadiusPassword, nil), }, }, }, authField, consts.MountTypeRadius) @@ -72,9 +72,24 @@ func (l *AuthLoginRadius) LoginPath() string { } func (l *AuthLoginRadius) Init(d *schema.ResourceData, authField string) (AuthLogin, error) { + defaults := authDefaults{ + { + field: consts.FieldUsername, + envVars: []string{consts.EnvVarRadiusUsername}, + defaultVal: "", + }, + { + field: consts.FieldPassword, + envVars: []string{consts.EnvVarRadiusPassword}, + defaultVal: "", + }, + } if err := l.AuthLoginCommon.Init(d, authField, - func(data *schema.ResourceData) error { - return l.checkRequiredFields(d, consts.FieldUsername, consts.FieldPassword) + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.setDefaultFields(d, defaults, params) + }, + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.checkRequiredFields(d, params, consts.FieldUsername, consts.FieldPassword) }, ); err != nil { return nil, err diff --git a/internal/provider/auth_radius_test.go b/internal/provider/auth_radius_test.go index cf66714ab..94d2290f1 100644 --- a/internal/provider/auth_radius_test.go +++ b/internal/provider/auth_radius_test.go @@ -39,6 +39,29 @@ func TestAuthLoginRadius_Init(t *testing.T) { }, wantErr: false, }, + { + name: "basic-with-env", + authField: consts.FieldAuthLoginRadius, + raw: map[string]interface{}{ + consts.FieldAuthLoginRadius: []interface{}{ + map[string]interface{}{ + consts.FieldNamespace: "ns1", + }, + }, + }, + envVars: map[string]string{ + consts.EnvVarRadiusUsername: "alice", + consts.EnvVarRadiusPassword: "password1", + }, + expectParams: map[string]interface{}{ + consts.FieldNamespace: "ns1", + consts.FieldUseRootNamespace: false, + consts.FieldMount: consts.MountTypeRadius, + consts.FieldUsername: "alice", + consts.FieldPassword: "password1", + }, + wantErr: false, + }, { name: "error-missing-resource", authField: consts.FieldAuthLoginRadius, diff --git a/internal/provider/auth_test.go b/internal/provider/auth_test.go index fd5315a08..b73d45cdf 100644 --- a/internal/provider/auth_test.go +++ b/internal/provider/auth_test.go @@ -8,6 +8,7 @@ import ( "io" "net" "net/http" + "os" "reflect" "testing" @@ -316,3 +317,195 @@ func TestAuthLoginCommon_Namespace(t *testing.T) { }) } } + +func TestAuthLoginCommon_setDefaultFields(t *testing.T) { + tests := []struct { + name string + params map[string]interface{} + expectParams map[string]interface{} + setEnv map[string]string + defaults authDefaults + }{ + { + name: "default-unset-and-env-unset", + params: map[string]interface{}{ + "foo": "", + }, + defaults: authDefaults{ + { + field: "foo", + envVars: []string{"TEST_TERRAFORM_VAULT_PROVIDER_FOO"}, + defaultVal: "", + }, + }, + expectParams: map[string]interface{}{ + "foo": "", + }, + }, + { + name: "default-set-and-env-unset", + params: map[string]interface{}{ + "foo": "", + }, + defaults: authDefaults{ + { + field: "foo", + envVars: []string{"TEST_TERRAFORM_VAULT_PROVIDER_FOO"}, + defaultVal: "bar", + }, + }, + expectParams: map[string]interface{}{ + "foo": "bar", + }, + }, + { + name: "default-set-and-env-set", + params: map[string]interface{}{ + "foo": "", + }, + defaults: authDefaults{ + { + field: "foo", + envVars: []string{"TEST_TERRAFORM_VAULT_PROVIDER_FOO"}, + defaultVal: "bar", + }, + }, + // env vars should override authDefault.defaultVal + setEnv: map[string]string{ + "TEST_TERRAFORM_VAULT_PROVIDER_FOO": "baz", + }, + expectParams: map[string]interface{}{ + "foo": "baz", + }, + }, + { + name: "default-unset-and-env-set", + params: map[string]interface{}{ + "foo": "", + }, + defaults: authDefaults{ + { + field: "foo", + envVars: []string{"TEST_TERRAFORM_VAULT_PROVIDER_FOO"}, + defaultVal: "", + }, + }, + // env vars should override authDefault.defaultVal + setEnv: map[string]string{ + "TEST_TERRAFORM_VAULT_PROVIDER_FOO": "baz", + }, + expectParams: map[string]interface{}{ + "foo": "baz", + }, + }, + { + name: "multiple-params-default-set-and-env-set", + params: map[string]interface{}{ + "foo": "", + "dog": "", + }, + defaults: authDefaults{ + { + field: "foo", + envVars: []string{"TEST_TERRAFORM_VAULT_PROVIDER_FOO"}, + defaultVal: "bar", + }, + { + field: "dog", + envVars: []string{"TEST_TERRAFORM_VAULT_PROVIDER_DOG"}, + defaultVal: "bark", + }, + }, + // env vars should override authDefault.defaultVal + setEnv: map[string]string{ + "TEST_TERRAFORM_VAULT_PROVIDER_FOO": "baz", + "TEST_TERRAFORM_VAULT_PROVIDER_DOG": "woof", + }, + expectParams: map[string]interface{}{ + "foo": "baz", + "dog": "woof", + }, + }, + { + name: "multiple-params-mixed-set-and-unset", + params: map[string]interface{}{ + "foo": "", + "dog": "", + }, + defaults: authDefaults{ + { + field: "foo", + envVars: []string{"TEST_TERRAFORM_VAULT_PROVIDER_FOO"}, + defaultVal: "bar", + }, + { + field: "dog", + envVars: []string{"TEST_TERRAFORM_VAULT_PROVIDER_DOG"}, + defaultVal: "bark", + }, + }, + // env vars should override authDefault.defaultVal + setEnv: map[string]string{ + "TEST_TERRAFORM_VAULT_PROVIDER_FOO": "baz", + }, + expectParams: map[string]interface{}{ + "foo": "baz", + "dog": "bark", + }, + }, + { + name: "multiple-env", + params: map[string]interface{}{ + "foo": "", + }, + defaults: authDefaults{ + { + field: "foo", + envVars: []string{"TEST_TERRAFORM_VAULT_PROVIDER_FOO", "TEST_TERRAFORM_VAULT_PROVIDER_QUX"}, + defaultVal: "", + }, + }, + setEnv: map[string]string{ + "TEST_TERRAFORM_VAULT_PROVIDER_QUX": "qux", + }, + expectParams: map[string]interface{}{ + "foo": "qux", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setEnv != nil { + for k, v := range tt.setEnv { + t.Setenv(k, v) + t.Cleanup(func() { + err := os.Unsetenv(k) + if err != nil { + t.Fatalf("could not unset env, err: %v", err) + } + }, + ) + } + } + l := &AuthLoginCommon{ + params: tt.params, + initialized: true, + } + + rootProvider := NewProvider(nil, nil) + pr := &schema.Resource{ + Schema: rootProvider.Schema, + } + d := pr.TestResourceData() + + err := l.setDefaultFields(d, tt.defaults, tt.params) + if err != nil { + t.Errorf("setDefaultFields() err: %v", err) + } + + if !reflect.DeepEqual(tt.expectParams, l.Params()) { + t.Errorf("setDefaultFields() expected params %#v, actual %#v", tt.expectParams, l.Params()) + } + }) + } +} diff --git a/internal/provider/auth_token_file.go b/internal/provider/auth_token_file.go index 4c7a6ba06..592c58a48 100644 --- a/internal/provider/auth_token_file.go +++ b/internal/provider/auth_token_file.go @@ -41,9 +41,9 @@ func GetTokenFileSchemaResource(authField string) *schema.Resource { return mustAddLoginSchema(&schema.Resource{ Schema: map[string]*schema.Schema{ consts.FieldFilename: { - Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc(consts.EnvVarTokenFilename, nil), + Type: schema.TypeString, + // can be set via an env var + Optional: true, Description: "The name of a file containing a single " + "line that is a valid Vault token", }, @@ -72,9 +72,19 @@ func (l *AuthLoginTokenFile) Init(d *schema.ResourceData, ) (AuthLogin, error) { l.mount = consts.MountTypeNone + defaults := authDefaults{ + { + field: consts.FieldFilename, + envVars: []string{consts.EnvVarTokenFilename}, + defaultVal: "", + }, + } if err := l.AuthLoginCommon.Init(d, authField, - func(data *schema.ResourceData) error { - return l.checkRequiredFields(d, consts.FieldFilename) + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.setDefaultFields(d, defaults, params) + }, + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.checkRequiredFields(d, params, consts.FieldFilename) }, ); err != nil { return nil, err diff --git a/internal/provider/auth_token_file_test.go b/internal/provider/auth_token_file_test.go index 944eed7b6..61da9d129 100644 --- a/internal/provider/auth_token_file_test.go +++ b/internal/provider/auth_token_file_test.go @@ -49,9 +49,7 @@ func TestAuthLoginTokenFile_Init(t *testing.T) { consts.EnvVarTokenFilename: "/tmp/vault-token", }, expectParams: map[string]interface{}{ - consts.FieldNamespace: "", - consts.FieldUseRootNamespace: false, - consts.FieldFilename: "/tmp/vault-token", + consts.FieldFilename: "/tmp/vault-token", }, wantErr: false, }, diff --git a/internal/provider/auth_userpass.go b/internal/provider/auth_userpass.go index c7c82e427..d4e315236 100644 --- a/internal/provider/auth_userpass.go +++ b/internal/provider/auth_userpass.go @@ -40,22 +40,20 @@ func GetUserpassLoginSchemaResource(authField string) *schema.Resource { return mustAddLoginSchema(&schema.Resource{ Schema: map[string]*schema.Schema{ consts.FieldUsername: { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + // can be set via an env var + Optional: true, Description: "Login with username", - DefaultFunc: schema.EnvDefaultFunc(consts.EnvVarUsername, nil), }, consts.FieldPassword: { Type: schema.TypeString, Optional: true, Description: "Login with password", - DefaultFunc: schema.EnvDefaultFunc(consts.EnvVarPassword, nil), }, consts.FieldPasswordFile: { Type: schema.TypeString, Optional: true, Description: "Login with password from a file", - DefaultFunc: schema.EnvDefaultFunc(consts.EnvVarPasswordFile, nil), // unfortunately the SDK does support conflicting relative fields // within a list type. As long as the top level schema does not change // we should be good to hard code fully qualified path like so. @@ -77,9 +75,30 @@ type AuthLoginUserpass struct { } func (l *AuthLoginUserpass) Init(d *schema.ResourceData, authField string) (AuthLogin, error) { + defaults := authDefaults{ + { + field: consts.FieldUsername, + envVars: []string{consts.EnvVarUsername}, + defaultVal: "", + }, + { + field: consts.FieldPassword, + envVars: []string{consts.EnvVarPassword}, + defaultVal: "", + }, + { + field: consts.FieldPasswordFile, + envVars: []string{consts.EnvVarPasswordFile}, + defaultVal: "", + }, + } + if err := l.AuthLoginCommon.Init(d, authField, - func(data *schema.ResourceData) error { - return l.checkRequiredFields(d, consts.FieldUsername) + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.setDefaultFields(d, defaults, params) + }, + func(data *schema.ResourceData, params map[string]interface{}) error { + return l.checkRequiredFields(d, params, consts.FieldUsername) }, ); err != nil { return nil, err diff --git a/internal/provider/fwprovider/auth.go b/internal/provider/fwprovider/auth.go new file mode 100644 index 000000000..a13812201 --- /dev/null +++ b/internal/provider/fwprovider/auth.go @@ -0,0 +1,61 @@ +package fwprovider + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-provider-vault/internal/consts" + "github.com/hashicorp/terraform-provider-vault/internal/framework/validators" +) + +func mustAddLoginSchema(s *schema.ListNestedBlock, defaultMount string) schema.Block { + m := map[string]schema.Attribute{ + consts.FieldNamespace: schema.StringAttribute{ + Optional: true, + Description: fmt.Sprintf( + "The authentication engine's namespace. Conflicts with %s", + consts.FieldUseRootNamespace, + ), + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.MatchRelative().AtParent().AtName(consts.FieldUseRootNamespace), + ), + }, + }, + consts.FieldUseRootNamespace: schema.BoolAttribute{ + Optional: true, + Description: fmt.Sprintf( + "Authenticate to the root Vault namespace. Conflicts with %s", + consts.FieldNamespace, + ), + Validators: []validator.Bool{ + boolvalidator.ConflictsWith( + path.MatchRelative().AtParent().AtName(consts.FieldUseRootNamespace), + ), + }, + }, + } + if defaultMount != consts.MountTypeNone { + m[consts.FieldMount] = &schema.StringAttribute{ + Optional: true, + Description: "The path where the authentication engine is mounted.", + Validators: []validator.String{ + validators.PathValidator(), + }, + } + } + + for k, v := range m { + if _, ok := s.NestedObject.Attributes[k]; ok { + panic(fmt.Sprintf("cannot add schema field %q, already exists in the Schema map", k)) + } + + s.NestedObject.Attributes[k] = v + } + + return s +} diff --git a/internal/provider/fwprovider/auth_aws.go b/internal/provider/fwprovider/auth_aws.go new file mode 100644 index 000000000..8f5b81039 --- /dev/null +++ b/internal/provider/fwprovider/auth_aws.go @@ -0,0 +1,82 @@ +package fwprovider + +import ( + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-provider-vault/internal/consts" + "github.com/hashicorp/terraform-provider-vault/internal/framework/validators" +) + +func AuthLoginAWSSchema() schema.Block { + return mustAddLoginSchema(&schema.ListNestedBlock{ + Description: "Login to vault using the AWS method", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + consts.FieldRole: schema.StringAttribute{ + Required: true, + Description: `The Vault role to use when logging into Vault.`, + }, + // static credential fields + consts.FieldAWSAccessKeyID: schema.StringAttribute{ + Optional: true, + Description: `The AWS access key ID.`, + }, + consts.FieldAWSSecretAccessKey: schema.StringAttribute{ + Optional: true, + Description: `The AWS secret access key.`, + }, + consts.FieldAWSSessionToken: schema.StringAttribute{ + Optional: true, + Description: `The AWS session token.`, + }, + consts.FieldAWSProfile: schema.StringAttribute{ + Optional: true, + Description: `The name of the AWS profile.`, + }, + consts.FieldAWSSharedCredentialsFile: schema.StringAttribute{ + Optional: true, + Description: `Path to the AWS shared credentials file.`, + }, + consts.FieldAWSWebIdentityTokenFile: schema.StringAttribute{ + Optional: true, + Description: `Path to the file containing an OAuth 2.0 access token or OpenID ` + + `Connect ID token.`, + }, + // STS assume role fields + consts.FieldAWSRoleARN: schema.StringAttribute{ + Optional: true, + Description: `The ARN of the AWS Role to assume.` + + `Used during STS AssumeRole`, + }, + consts.FieldAWSRoleSessionName: schema.StringAttribute{ + Optional: true, + Description: `Specifies the name to attach to the AWS role session. ` + + `Used during STS AssumeRole`, + }, + consts.FieldAWSRegion: schema.StringAttribute{ + Optional: true, + Description: `The AWS region.`, + }, + consts.FieldAWSSTSEndpoint: schema.StringAttribute{ + Optional: true, + Description: `The STS endpoint URL.`, + Validators: []validator.String{ + validators.URIValidator([]string{"http", "https"}), + }, + }, + consts.FieldAWSIAMEndpoint: schema.StringAttribute{ + Optional: true, + Description: `The IAM endpoint URL.`, + Validators: []validator.String{ + validators.URIValidator([]string{"http", "https"}), + }, + }, + consts.FieldHeaderValue: schema.StringAttribute{ + Optional: true, + Description: `The Vault header value to include in the STS signing request.`, + }, + }, + }, + }, consts.MountTypeAWS) +} diff --git a/internal/provider/fwprovider/auth_azure.go b/internal/provider/fwprovider/auth_azure.go new file mode 100644 index 000000000..d94c83d9f --- /dev/null +++ b/internal/provider/fwprovider/auth_azure.go @@ -0,0 +1,82 @@ +package fwprovider + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-provider-vault/internal/consts" +) + +func AuthLoginAzureSchema() schema.Block { + return mustAddLoginSchema(&schema.ListNestedBlock{ + Description: "Login to vault using the azure method", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + consts.FieldJWT: schema.StringAttribute{ + Optional: true, + Description: "A signed JSON Web Token. If not specified on will be " + + "created automatically", + }, + consts.FieldRole: schema.StringAttribute{ + Required: true, + Description: "Name of the login role.", + }, + consts.FieldSubscriptionID: schema.StringAttribute{ + Required: true, + Description: "The subscription ID for the machine that generated the MSI token. " + + "This information can be obtained through instance metadata.", + }, + consts.FieldResourceGroupName: schema.StringAttribute{ + Required: true, + Description: "The resource group for the machine that generated the MSI token. " + + "This information can be obtained through instance metadata.", + }, + consts.FieldVMName: schema.StringAttribute{ + Optional: true, + Description: "The virtual machine name for the machine that generated the MSI token. " + + "This information can be obtained through instance metadata.", + }, + consts.FieldVMSSName: schema.StringAttribute{ + Optional: true, + Description: "The virtual machine scale set name for the machine that generated " + + "the MSI token. This information can be obtained through instance metadata.", + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.MatchRelative().AtName(consts.FieldVMName), + ), + }, + }, + consts.FieldTenantID: schema.StringAttribute{ + Optional: true, + Description: "Provides the tenant ID to use in a multi-tenant " + + "authentication scenario.", + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.MatchRelative().AtName(consts.FieldJWT), + ), + }, + }, + consts.FieldClientID: schema.StringAttribute{ + Optional: true, + Description: "The identity's client ID.", + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.MatchRelative().AtName(consts.FieldJWT), + ), + }, + }, + consts.FieldScope: schema.StringAttribute{ + Optional: true, + Description: "The scopes to include in the token request.", + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.MatchRelative().AtName(consts.FieldJWT), + ), + }, + }, + }, + }, + }, consts.MountTypeAzure) +} diff --git a/internal/provider/fwprovider/auth_cert.go b/internal/provider/fwprovider/auth_cert.go new file mode 100644 index 000000000..9cffd51a1 --- /dev/null +++ b/internal/provider/fwprovider/auth_cert.go @@ -0,0 +1,29 @@ +package fwprovider + +import ( + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + + "github.com/hashicorp/terraform-provider-vault/internal/consts" +) + +func AuthLoginCertSchema() schema.Block { + return mustAddLoginSchema(&schema.ListNestedBlock{ + Description: "Login to vault using the cert method", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + consts.FieldName: schema.StringAttribute{ + Optional: true, + Description: "Name of the certificate's role", + }, + consts.FieldCertFile: schema.StringAttribute{ + Required: true, + Description: "Path to a file containing the client certificate.", + }, + consts.FieldKeyFile: schema.StringAttribute{ + Required: true, + Description: "Path to a file containing the private key that the certificate was issued for.", + }, + }, + }, + }, consts.MountTypeCert) +} diff --git a/internal/provider/fwprovider/auth_gcp.go b/internal/provider/fwprovider/auth_gcp.go new file mode 100644 index 000000000..37d1f8a5d --- /dev/null +++ b/internal/provider/fwprovider/auth_gcp.go @@ -0,0 +1,54 @@ +package fwprovider + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-provider-vault/internal/consts" + "github.com/hashicorp/terraform-provider-vault/internal/framework/validators" +) + +func AuthLoginGCPSchema() schema.Block { + return mustAddLoginSchema(&schema.ListNestedBlock{ + Description: "Login to vault using the gcp method", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + consts.FieldRole: schema.StringAttribute{ + Required: true, + Description: "Name of the login role.", + }, + consts.FieldJWT: schema.StringAttribute{ + Optional: true, + Description: "A signed JSON Web Token.", + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.MatchRelative().AtName(consts.FieldCredentials), + ), + }, + }, + consts.FieldCredentials: schema.StringAttribute{ + Optional: true, + Description: "Path to the Google Cloud credentials file.", + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.MatchRelative().AtName(consts.FieldJWT), + ), + stringvalidator.LengthAtLeast(1), + validators.GCPCredentialsValidator(), + }, + }, + consts.FieldServiceAccount: schema.StringAttribute{ + Optional: true, + Description: "IAM service account.", + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.MatchRelative().AtName(consts.FieldJWT), + ), + }, + }, + }, + }, + }, consts.MountTypeGCP) +} diff --git a/internal/provider/fwprovider/auth_generic.go b/internal/provider/fwprovider/auth_generic.go new file mode 100644 index 000000000..a5e04e117 --- /dev/null +++ b/internal/provider/fwprovider/auth_generic.go @@ -0,0 +1,29 @@ +package fwprovider + +import ( + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-provider-vault/internal/consts" +) + +func AuthLoginGenericSchema() schema.Block { + return mustAddLoginSchema(&schema.ListNestedBlock{ + Description: "Login to vault with an existing auth method using auth//login", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + consts.FieldPath: schema.StringAttribute{ + Required: true, + }, + consts.FieldParameters: schema.MapAttribute{ + Optional: true, + ElementType: types.StringType, + Sensitive: true, + }, + consts.FieldMethod: schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, consts.MountTypeNone) +} diff --git a/internal/provider/fwprovider/auth_jwt.go b/internal/provider/fwprovider/auth_jwt.go new file mode 100644 index 000000000..d8527577f --- /dev/null +++ b/internal/provider/fwprovider/auth_jwt.go @@ -0,0 +1,26 @@ +package fwprovider + +import ( + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + + "github.com/hashicorp/terraform-provider-vault/internal/consts" +) + +func AuthLoginJWTSchema() schema.Block { + return mustAddLoginSchema(&schema.ListNestedBlock{ + Description: "Login to vault using the jwt method", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + consts.FieldRole: schema.StringAttribute{ + Required: true, + Description: "Name of the login role.", + }, + consts.FieldJWT: schema.StringAttribute{ + // can be set via an env var + Optional: true, + Description: "A signed JSON Web Token.", + }, + }, + }, + }, consts.MountTypeJWT) +} diff --git a/internal/provider/fwprovider/auth_kerberos.go b/internal/provider/fwprovider/auth_kerberos.go new file mode 100644 index 000000000..9f591866c --- /dev/null +++ b/internal/provider/fwprovider/auth_kerberos.go @@ -0,0 +1,94 @@ +package fwprovider + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-provider-vault/internal/consts" + "github.com/hashicorp/terraform-provider-vault/internal/framework/validators" +) + +func AuthLoginKerberosSchema() schema.Block { + return mustAddLoginSchema(&schema.ListNestedBlock{ + Description: "Login to vault using the kerberos method", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + consts.FieldToken: schema.StringAttribute{ + Optional: true, + Description: "Simple and Protected GSSAPI Negotiation Mechanism (SPNEGO) token", + Validators: []validator.String{ + validators.KRBNegTokenValidator(), + }, + }, + consts.FieldUsername: schema.StringAttribute{ + Optional: true, + Description: "The username to login into Kerberos with.", + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.MatchRelative().AtName(consts.FieldToken), + ), + }, + }, + consts.FieldService: schema.StringAttribute{ + Optional: true, + Description: "The service principle name.", + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.MatchRelative().AtName(consts.FieldToken), + ), + }, + }, + consts.FieldRealm: schema.StringAttribute{ + Optional: true, + Description: "The Kerberos server's authoritative authentication domain", + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.MatchRelative().AtName(consts.FieldToken), + ), + }, + }, + consts.FieldKRB5ConfPath: schema.StringAttribute{ + Optional: true, + Description: "A valid Kerberos configuration file e.g. /etc/krb5.conf.", + Validators: []validator.String{ + validators.FileExistsValidator(), + stringvalidator.ConflictsWith( + path.MatchRelative().AtName(consts.FieldToken), + ), + }, + }, + consts.FieldKeytabPath: schema.StringAttribute{ + Optional: true, + Description: "The Kerberos keytab file containing the entry of the login entity.", + Validators: []validator.String{ + validators.FileExistsValidator(), + stringvalidator.ConflictsWith( + path.MatchRelative().AtName(consts.FieldToken), + ), + }, + }, + consts.FieldDisableFastNegotiation: schema.BoolAttribute{ + Optional: true, + Validators: []validator.Bool{ + boolvalidator.ConflictsWith( + path.MatchRelative().AtName(consts.FieldToken), + ), + }, + Description: "Disable the Kerberos FAST negotiation.", + }, + consts.FieldRemoveInstanceName: schema.BoolAttribute{ + Optional: true, + Validators: []validator.Bool{ + boolvalidator.ConflictsWith( + path.MatchRelative().AtName(consts.FieldToken), + ), + }, + Description: "Strip the host from the username found in the keytab.", + }, + }, + }, + }, consts.MountTypeKerberos) +} diff --git a/internal/provider/fwprovider/auth_oci.go b/internal/provider/fwprovider/auth_oci.go new file mode 100644 index 000000000..bbd640dc0 --- /dev/null +++ b/internal/provider/fwprovider/auth_oci.go @@ -0,0 +1,35 @@ +package fwprovider + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-provider-vault/internal/consts" +) + +const ( + ociAuthTypeInstance = "instance" + ociAuthTypeAPIKeys = "apikey" +) + +func AuthLoginOCISchema() schema.Block { + return mustAddLoginSchema(&schema.ListNestedBlock{ + Description: "Login to vault using the OCI method", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + consts.FieldRole: schema.StringAttribute{ + Required: true, + Description: "Name of the login role.", + }, + consts.FieldAuthType: schema.StringAttribute{ + Required: true, + Description: "Authentication type to use when getting OCI credentials.", + Validators: []validator.String{ + stringvalidator.OneOf([]string{ociAuthTypeInstance, ociAuthTypeAPIKeys}...), + }, + }, + }, + }, + }, consts.MountTypeOCI) +} diff --git a/internal/provider/fwprovider/auth_oidc.go b/internal/provider/fwprovider/auth_oidc.go new file mode 100644 index 000000000..5a90185dd --- /dev/null +++ b/internal/provider/fwprovider/auth_oidc.go @@ -0,0 +1,38 @@ +package fwprovider + +import ( + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-provider-vault/internal/consts" + "github.com/hashicorp/terraform-provider-vault/internal/framework/validators" +) + +func AuthLoginOIDCSchema() schema.Block { + return mustAddLoginSchema(&schema.ListNestedBlock{ + Description: "Login to vault using the oidc method", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + consts.FieldRole: schema.StringAttribute{ + Required: true, + Description: "Name of the login role.", + }, + + consts.FieldCallbackListenerAddress: schema.StringAttribute{ + Optional: true, + Description: "The callback listener's address. Must be a valid URI without the path.", + Validators: []validator.String{ + validators.URIValidator([]string{"tcp"}), + }, + }, + consts.FieldCallbackAddress: schema.StringAttribute{ + Optional: true, + Description: "The callback address. Must be a valid URI without the path.", + Validators: []validator.String{ + validators.URIValidator([]string{"http", "https"}), + }, + }, + }, + }, + }, consts.MountTypeOIDC) +} diff --git a/internal/provider/fwprovider/auth_radius.go b/internal/provider/fwprovider/auth_radius.go new file mode 100644 index 000000000..d18b52442 --- /dev/null +++ b/internal/provider/fwprovider/auth_radius.go @@ -0,0 +1,27 @@ +package fwprovider + +import ( + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + + "github.com/hashicorp/terraform-provider-vault/internal/consts" +) + +func AuthLoginRadiusSchema() schema.Block { + return mustAddLoginSchema(&schema.ListNestedBlock{ + Description: "Login to vault using the radius method", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + consts.FieldUsername: schema.StringAttribute{ + Description: "The Radius username.", + // can be set via an env var + Optional: true, + }, + consts.FieldPassword: schema.StringAttribute{ + // can be set via an env var + Optional: true, + Description: "The Radius password for username.", + }, + }, + }, + }, consts.MountTypeRadius) +} diff --git a/internal/provider/fwprovider/auth_token_file.go b/internal/provider/fwprovider/auth_token_file.go new file mode 100644 index 000000000..9ea611b48 --- /dev/null +++ b/internal/provider/fwprovider/auth_token_file.go @@ -0,0 +1,22 @@ +package fwprovider + +import ( + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-provider-vault/internal/consts" +) + +func AuthLoginTokenFileSchema() schema.Block { + return mustAddLoginSchema(&schema.ListNestedBlock{ + Description: "Login to vault using ", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + consts.FieldFilename: schema.StringAttribute{ + // can be set via an env var + Optional: true, + Description: "The name of a file containing a single " + + "line that is a valid Vault token", + }, + }, + }, + }, consts.MountTypeNone) +} diff --git a/internal/provider/fwprovider/auth_userpass.go b/internal/provider/fwprovider/auth_userpass.go new file mode 100644 index 000000000..3f810d755 --- /dev/null +++ b/internal/provider/fwprovider/auth_userpass.go @@ -0,0 +1,38 @@ +package fwprovider + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-provider-vault/internal/consts" +) + +func AuthLoginUserpassSchema() schema.Block { + return mustAddLoginSchema(&schema.ListNestedBlock{ + Description: "Login to vault using the userpass method", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + consts.FieldUsername: schema.StringAttribute{ + // can be set via an env var + Optional: true, + Description: "Login with username", + }, + consts.FieldPassword: schema.StringAttribute{ + Optional: true, + Description: "Login with password", + }, + consts.FieldPasswordFile: schema.StringAttribute{ + Optional: true, + Description: "Login with password from a file", + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.MatchRelative().AtName(consts.FieldPassword), + ), + }, + }, + }, + }, + }, consts.MountTypeUserpass) +} diff --git a/internal/provider/fwprovider/provider.go b/internal/provider/fwprovider/provider.go new file mode 100644 index 000000000..1b8adcdaf --- /dev/null +++ b/internal/provider/fwprovider/provider.go @@ -0,0 +1,210 @@ +package fwprovider + +import ( + "context" + "fmt" + "regexp" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-provider-vault/internal/consts" + sdkv2provider "github.com/hashicorp/terraform-provider-vault/internal/provider" + "github.com/hashicorp/terraform-provider-vault/internal/vault/sys" +) + +// Ensure the implementation satisfies the provider.Provider interface +var _ provider.Provider = &fwprovider{} + +// New returns a new, initialized Terraform Plugin Framework-style provider instance. +// +// The provider instance is fully configured once the `Configure` method has been called. +func New(primary interface{ Meta() interface{} }) provider.Provider { + return &fwprovider{ + Primary: primary, + } +} + +// Provider implements the terraform-plugin-framework's provider.Provider +// interface +// +// See: https://developer.hashicorp.com/terraform/plugin/framework +type fwprovider struct { + Primary interface{ Meta() interface{} } +} + +// Metadata returns the metadata for the provider, such as a type name and +// version data. +func (p *fwprovider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "vault" + // TODO: inject provider version during build time + // resp.Version = "0.0.0-dev" +} + +// Schema returns the schema for this provider's configuration. +// +// Schema is called during validate, plan and apply. +func (p *fwprovider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + // This schema must match exactly to the SDKv2 provider's schema + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + // Not `Required` but must be set via config or env. Otherwise we + // return an error. + consts.FieldAddress: schema.StringAttribute{ + Optional: true, + Description: "URL of the root of the target Vault server.", + }, + "add_address_to_env": schema.StringAttribute{ + Optional: true, + Description: "If true, adds the value of the `address` argument to the Terraform process environment.", + }, + // Not `Required` but must be set via config, env, or token helper. + // Otherwise we return an error. + "token": schema.StringAttribute{ + Optional: true, + Description: "Token to use to authenticate to Vault.", + }, + "token_name": schema.StringAttribute{ + Optional: true, + Description: "Token name to use for creating the Vault child token.", + }, + "skip_child_token": schema.BoolAttribute{ + Optional: true, + + // Setting to true will cause max_lease_ttl_seconds and token_name to be ignored (not used). + // Note that this is strongly discouraged due to the potential of exposing sensitive secret data. + Description: "Set this to true to prevent the creation of ephemeral child token used by this provider.", + }, + consts.FieldCACertFile: schema.StringAttribute{ + Optional: true, + Description: "Path to a CA certificate file to validate the server's certificate.", + }, + consts.FieldCACertDir: schema.StringAttribute{ + Optional: true, + Description: "Path to directory containing CA certificate files to validate the server's certificate.", + }, + consts.FieldSkipTLSVerify: schema.BoolAttribute{ + Optional: true, + Description: "Set this to true only if the target Vault server is an insecure development instance.", + }, + consts.FieldTLSServerName: schema.StringAttribute{ + Optional: true, + Description: "Name to use as the SNI host when connecting via TLS.", + }, + "max_lease_ttl_seconds": schema.Int64Attribute{ + Optional: true, + Description: "Maximum TTL for secret leases requested by this provider.", + }, + "max_retries": schema.Int64Attribute{ + Optional: true, + Description: "Maximum number of retries when a 5xx error code is encountered.", + }, + "max_retries_ccc": schema.Int64Attribute{ + Optional: true, + Description: "Maximum number of retries for Client Controlled Consistency related operations", + }, + consts.FieldNamespace: schema.StringAttribute{ + Optional: true, + Description: "The namespace to use. Available only for Vault Enterprise.", + }, + consts.FieldSkipGetVaultVersion: schema.BoolAttribute{ + Optional: true, + Description: "Skip the dynamic fetching of the Vault server version.", + }, + consts.FieldVaultVersionOverride: schema.StringAttribute{ + Optional: true, + Description: "Override the Vault server version, " + + "which is normally determined dynamically from the target Vault server", + Validators: []validator.String{ + // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + stringvalidator.RegexMatches( + regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`), + "must be a valid semantic version", + ), + }, + }, + consts.FieldSetNamespaceFromToken: schema.BoolAttribute{ + Optional: true, + Description: "In the case where the Vault token is for a specific namespace " + + "and the provider namespace is not configured, use the token namespace " + + "as the root namespace for all resources.", + }, + }, + Blocks: map[string]schema.Block{ + "headers": schema.ListNestedBlock{ + Description: "The headers to send with each Vault request.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + Sensitive: true, + Description: "The header name", + }, + "value": schema.StringAttribute{ + Required: true, + Sensitive: true, + Description: "The header value", + }, + }, + }, + }, + consts.FieldAuthLoginAWS: AuthLoginAWSSchema(), + consts.FieldAuthLoginAzure: AuthLoginAzureSchema(), + consts.FieldAuthLoginCert: AuthLoginCertSchema(), + consts.FieldAuthLoginGCP: AuthLoginGCPSchema(), + consts.FieldAuthLoginGeneric: AuthLoginGenericSchema(), + consts.FieldAuthLoginJWT: AuthLoginJWTSchema(), + consts.FieldAuthLoginKerberos: AuthLoginKerberosSchema(), + consts.FieldAuthLoginOCI: AuthLoginOCISchema(), + consts.FieldAuthLoginOIDC: AuthLoginOIDCSchema(), + consts.FieldAuthLoginRadius: AuthLoginRadiusSchema(), + consts.FieldAuthLoginTokenFile: AuthLoginTokenFileSchema(), + consts.FieldAuthLoginUserpass: AuthLoginUserpassSchema(), + }, + } +} + +// Configure handles the configuration of any provider-level data or clients. +// These configuration values may be from the practitioner Terraform +// configuration, environment variables, or other means such as reading +// vendor-specific configuration files. +// +// Configure is called during plan and apply. +func (p *fwprovider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + // Provider's parsed configuration (its instance state) is available + // through the primary provider's Meta() method. + v, ok := p.Primary.Meta().(*sdkv2provider.ProviderMeta) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *provider.ProviderMeta, got: %T. Please report this issue to the provider developers.", p.Primary.Meta()), + ) + return + } + resp.DataSourceData = v + resp.ResourceData = v +} + +// Resources returns a slice of functions to instantiate each Resource +// implementation. +// +// The resource type name is determined by the Resource implementing +// the Metadata method. All resources must have unique names. +func (p *fwprovider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + sys.NewPasswordPolicyResource, + } +} + +// DataSources returns a slice of functions to instantiate each DataSource +// implementation. +// +// The data source type name is determined by the DataSource implementing +// the Metadata method. All data sources must have unique names. +func (p *fwprovider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{} +} diff --git a/internal/provider/meta.go b/internal/provider/meta.go index b7cb02edd..d920fa71e 100644 --- a/internal/provider/meta.go +++ b/internal/provider/meta.go @@ -9,6 +9,7 @@ import ( "log" "net/http" "os" + "strconv" "strings" "sync" "time" @@ -184,27 +185,20 @@ func (p *ProviderMeta) setClient() error { d := p.resourceData clientConfig := api.DefaultConfig() - addr := d.Get(consts.FieldAddress).(string) - if addr != "" { - clientConfig.Address = addr + + addr := GetResourceDataStr(d, consts.FieldAddress, api.EnvVaultAddress, "") + if addr == "" { + return fmt.Errorf("failed to configure Vault address") } + clientConfig.Address = addr + clientConfig.CloneTLSConfig = true tlsConfig := &api.TLSConfig{ - CACert: d.Get(consts.FieldCACertFile).(string), - CAPath: d.Get(consts.FieldCACertDir).(string), - Insecure: d.Get(consts.FieldSkipTLSVerify).(bool), - TLSServerName: d.Get(consts.FieldTLSServerName).(string), - } - - if _, ok := d.GetOk(consts.FieldClientAuth); ok { - prefix := fmt.Sprintf("%s.0.", consts.FieldClientAuth) - if v, ok := d.GetOk(prefix + consts.FieldCertFile); ok { - tlsConfig.ClientCert = v.(string) - } - if v, ok := d.GetOk(prefix + consts.FieldKeyFile); ok { - tlsConfig.ClientKey = v.(string) - } + CACert: GetResourceDataStr(d, consts.FieldCACertFile, api.EnvVaultCACert, ""), + CAPath: GetResourceDataStr(d, consts.FieldCACertDir, api.EnvVaultCAPath, ""), + Insecure: GetResourceDataBool(d, consts.FieldSkipTLSVerify, "VAULT_SKIP_VERIFY", false), + TLSServerName: GetResourceDataStr(d, consts.FieldTLSServerName, api.EnvVaultTLSServerName, ""), } err := clientConfig.ConfigureTLS(tlsConfig) @@ -251,12 +245,12 @@ func (p *ProviderMeta) setClient() error { } client.SetHeaders(parsedHeaders) - client.SetMaxRetries(d.Get("max_retries").(int)) + client.SetMaxRetries(GetResourceDataInt(d, "max_retries", "VAULT_MAX_RETRIES", DefaultMaxHTTPRetries)) - MaxHTTPRetriesCCC = d.Get("max_retries_ccc").(int) + MaxHTTPRetriesCCC = GetResourceDataInt(d, "max_retries_ccc", "VAULT_MAX_RETRIES_CCC", DefaultMaxHTTPRetriesCCC) // Set the namespace to the requested namespace, if provided - namespace := d.Get(consts.FieldNamespace).(string) + namespace := GetResourceDataStr(d, consts.FieldNamespace, "VAULT_NAMESPACE", "") authLogin, err := GetAuthLogin(d) if err != nil { @@ -325,7 +319,9 @@ func (p *ProviderMeta) setClient() error { tokenNamespace = strings.Trim(v.(string), "/") } - if !d.Get(consts.FieldSkipChildToken).(bool) { + skipChildToken := GetResourceDataBool(d, consts.FieldSkipChildToken, "TERRAFORM_VAULT_SKIP_CHILD_TOKEN", false) + log.Printf("[VINAY] Skip Child Token %t", skipChildToken) + if !skipChildToken { // a child token is always created in the namespace of the parent token. token, err = createChildToken(d, client, tokenNamespace) if err != nil { @@ -348,7 +344,8 @@ func (p *ProviderMeta) setClient() error { namespace = tokenNamespace // set the namespace on the provider to ensure that all child // namespace paths are properly honoured. - if v, ok := d.Get(consts.FieldSetNamespaceFromToken).(bool); ok && v { + setTokenFromNamespace := GetResourceDataBool(d, consts.FieldSetNamespaceFromToken, "VAULT_SET_NAMESPACE_FROM_TOKEN", true) + if setTokenFromNamespace { if err := d.Set(consts.FieldNamespace, namespace); err != nil { return err } @@ -554,10 +551,7 @@ func getVaultVersion(client *api.Client) (*version.Version, error) { } func createChildToken(d *schema.ResourceData, c *api.Client, namespace string) (string, error) { - tokenName := d.Get("token_name").(string) - if tokenName == "" { - tokenName = "terraform" - } + tokenName := GetResourceDataStr(d, "token_name", "VAULT_TOKEN_NAME", "terraform") // the clone is only used to auth to Vault clone, err := c.Clone() @@ -581,10 +575,11 @@ func createChildToken(d *schema.ResourceData, c *api.Client, namespace string) ( // Caution is still required with state files since not all secrets // can explicitly be revoked, and this limited scope won't apply to // any secrets that are *written* by Terraform to Vault. + ttl := GetResourceDataInt(d, consts.FieldMaxLeaseTTL, "TERRAFORM_VAULT_MAX_TTL", 1200) childTokenLease, err := clone.Auth().Token().Create(&api.TokenCreateRequest{ DisplayName: tokenName, - TTL: fmt.Sprintf("%ds", d.Get("max_lease_ttl_seconds").(int)), - ExplicitMaxTTL: fmt.Sprintf("%ds", d.Get("max_lease_ttl_seconds").(int)), + TTL: fmt.Sprintf("%ds", ttl), + ExplicitMaxTTL: fmt.Sprintf("%ds", ttl), Renewable: pointer.Bool(false), }) if err != nil { @@ -599,9 +594,83 @@ func createChildToken(d *schema.ResourceData, c *api.Client, namespace string) ( return childToken, nil } +// GetResourceDataStr returns the value for a given ResourceData field +// If the value is the zero value, then it checks the environment variable. If +// the environment variable is empty, the default dv is returned +func GetResourceDataStr(d *schema.ResourceData, field, env, dv string) string { + if s := d.Get(field).(string); s != "" { + return s + } + + if env != "" { + if s := os.Getenv(env); s != "" { + return s + } + } + + // return default + return dv +} + +// GetResourceDataInt returns the value for a given ResourceData field +// If the value is the zero value, then it checks the environment variable. If +// the environment variable is empty, the default dv is returned +func GetResourceDataInt(d *schema.ResourceData, field, env string, dv int) int { + if v := d.Get(field).(int); v != 0 { + return v + } + if env != "" { + if s := os.Getenv(env); s != "" { + ret, err := strconv.Atoi(s) + if err == nil { + return ret + } + // swallow the error and return the default because that is the + // behavior we had when using SDKv2's schema.EnvDefaultFunc + } + } + // return default + return dv +} + +// GetResourceDataBool returns the value for a given ResourceData field +// If the value is the zero value, then it checks the environment variable. If +// the environment variable is empty, the default dv is returned +func GetResourceDataBool(d *schema.ResourceData, field, env string, dv bool) bool { + // since Get does not tell us if the value is false or unset, + // we only return this value if it is non-nil, else we return the default + + rawConfig := d.GetRawConfig() + rawVal := rawConfig.GetAttr(field) + + // We don't care about the underlying value, just detecting if the config value is null (unset) or not. + if rawVal.IsNull() { + // The value is null (unset) in config, do our defaulting logic + + if env != "" { + if s := os.Getenv(env); s != "" { + ret, err := strconv.ParseBool(s) + if err == nil { + return ret + } + // swallow the error and return the default because that is the + // behavior we had when using SDKv2's schema.EnvDefaultFunc + } + } + + // return default if value not in environment + return dv + } + + // If the value is set in config, return using d.Get + return d.Get(field).(bool) +} + func GetToken(d *schema.ResourceData) (string, error) { if token := d.Get("token").(string); token != "" { return token, nil + } else if token = os.Getenv(api.EnvVaultToken); token != "" { + return token, nil } if addAddr := d.Get("add_address_to_env").(string); addAddr == "true" { diff --git a/internal/provider/meta_test.go b/internal/provider/meta_test.go index 983fa3811..4181674e0 100644 --- a/internal/provider/meta_test.go +++ b/internal/provider/meta_test.go @@ -536,7 +536,7 @@ func TestNewProviderMeta(t *testing.T) { testutil.SkipTestAccEnt(t) testutil.TestAccPreCheck(t) - nsPrefix := acctest.RandomWithPrefix("ns") + nsPrefix := acctest.RandomWithPrefix("ns") + "-" defaultUser := "alice" defaultPassword := "f00bazB1ff" @@ -550,6 +550,7 @@ func TestNewProviderMeta(t *testing.T) { name string d *schema.ResourceData data map[string]interface{} + env map[string]string wantNamespace string tokenNamespace string authLoginNamespace string @@ -644,9 +645,11 @@ func TestNewProviderMeta(t *testing.T) { name: "set-namespace-from-token-false", d: pr.TestResourceData(), data: map[string]interface{}{ - consts.FieldSkipGetVaultVersion: true, - consts.FieldSetNamespaceFromToken: false, - consts.FieldSkipChildToken: true, + consts.FieldSkipGetVaultVersion: true, + consts.FieldSkipChildToken: true, + }, + env: map[string]string{ + "VAULT_SET_NAMESPACE_FROM_TOKEN": "false", }, tokenNamespace: nsPrefix + "set-ns-from-token-auth-false-ignored", wantNamespace: nsPrefix + "set-ns-from-token-auth-false-ignored", @@ -659,9 +662,8 @@ func TestNewProviderMeta(t *testing.T) { name: "set-namespace-from-token-true", d: pr.TestResourceData(), data: map[string]interface{}{ - consts.FieldSkipGetVaultVersion: true, - consts.FieldSetNamespaceFromToken: true, - consts.FieldSkipChildToken: true, + consts.FieldSkipGetVaultVersion: true, + consts.FieldSkipChildToken: true, consts.FieldAuthLoginUserpass: []map[string]interface{}{ { consts.FieldNamespace: nsPrefix + "set-ns-from-token-auth-true", @@ -671,12 +673,32 @@ func TestNewProviderMeta(t *testing.T) { }, }, }, + env: map[string]string{ + "VAULT_SET_NAMESPACE_FROM_TOKEN": "true", + }, authLoginNamespace: nsPrefix + "set-ns-from-token-auth-true", wantNamespace: nsPrefix + "set-ns-from-token-auth-true", checkSetSetTokenNamespace: true, wantNamespaceFromToken: nsPrefix + "set-ns-from-token-auth-true", wantErr: false, }, + { + // expect token namespace to be ignored + name: "with-token-ns-only-override-env-false", + d: pr.TestResourceData(), + data: map[string]interface{}{ + consts.FieldSkipGetVaultVersion: true, + consts.FieldSkipChildToken: true, + }, + env: map[string]string{ + "VAULT_SET_NAMESPACE_FROM_TOKEN": "false", + }, + tokenNamespace: nsPrefix + "token-ns-only", + wantNamespace: nsPrefix + "token-ns-only", + checkSetSetTokenNamespace: true, + wantNamespaceFromToken: "", + wantErr: false, + }, } createNamespace := func(t *testing.T, client *api.Client, ns string) { @@ -704,9 +726,20 @@ func TestNewProviderMeta(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { - t.Parallel() + // we cannot run these in Parallel + // some of the test cases will set env vars that will cause flakiness + if tt.env != nil { + for k, v := range tt.env { + if err := os.Setenv(k, v); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + os.Unsetenv(k) + }) + } + } + if tt.authLoginNamespace != "" { createNamespace(t, client, tt.authLoginNamespace) options := &api.EnableAuthOptions{ @@ -806,7 +839,7 @@ func TestNewProviderMeta_Cert(t *testing.T) { testutil.SkipTestAccEnt(t) testutil.TestAccPreCheck(t) - nsPrefix := acctest.RandomWithPrefix("ns") + nsPrefix := acctest.RandomWithPrefix("ns") + "-" defaultUser := "alice" defaultPassword := "f00bazB1ff" @@ -1024,3 +1057,74 @@ func TestNewProviderMeta_Cert(t *testing.T) { }) } } + +func TestGetResourceDataBool(t *testing.T) { + testutil.TestAccPreCheck(t) + testutil.SkipTestAcc(t) + rootProvider := NewProvider(nil, nil) + + //pr := &schema.Resource{ + // Schema: rootProvider.Schema, + //} + + tests := []struct { + name string + field string + data map[string]interface{} + dv bool + env string + expected bool + }{ + { + name: "unset", + data: map[string]interface{}{ + consts.FieldSkipChildToken: true, + }, + field: consts.FieldSetNamespaceFromToken, + dv: true, + env: "VAULT_SET_NAMESPACE_FROM_TOKEN", + expected: true, + }, + { + name: "set-to-false", + data: map[string]interface{}{ + consts.FieldSetNamespaceFromToken: false, + }, + field: consts.FieldSetNamespaceFromToken, + dv: true, + env: "VAULT_SET_NAMESPACE_FROM_TOKEN", + expected: false, + }, + { + name: "set-to-true", + data: map[string]interface{}{ + consts.FieldSetNamespaceFromToken: true, + }, + field: consts.FieldSetNamespaceFromToken, + dv: true, + env: "VAULT_SET_NAMESPACE_FROM_TOKEN", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + //ctx := context.Background() + //cfg := terraform.NewResourceConfigRaw(tt.data) + //rootProvider.Configure(ctx, cfg) + diff := schema.TestResourceDataRaw(t, + rootProvider.Schema, + tt.data) + //for k, v := range tt.data { + // if err := tt.d.Set(k, v); err != nil { + // t.Fatalf("failed to set resource data, key=%s, value=%#v", k, v) + // } + //} + + got := GetResourceDataBool(diff, tt.field, tt.env, tt.dv) + if got != tt.expected { + t.Errorf("GetResourceDataBool() got = %v, want %v", got, tt.expected) + } + }) + } +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index b6f05fe10..7ccc51bb5 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -13,7 +13,6 @@ import ( "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/vault/api" "github.com/hashicorp/terraform-provider-vault/internal/consts" ) @@ -65,36 +64,36 @@ func NewProvider( } r := &schema.Provider{ + // This schema must match exactly the fwprovider (Terraform Plugin Framework) schema. + // Notably the attributes can have no Default values. Schema: map[string]*schema.Schema{ + // Not `Required` but must be set via config or env. Otherwise we + // return an error. consts.FieldAddress: { Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc(api.EnvVaultAddress, nil), + Optional: true, Description: "URL of the root of the target Vault server.", }, "add_address_to_env": { Type: schema.TypeString, Optional: true, - Default: false, Description: "If true, adds the value of the `address` argument to the Terraform process environment.", }, + // Not `Required` but must be set via config, env, or token helper. + // Otherwise we return an error. "token": { Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc(api.EnvVaultToken, ""), + Optional: true, Description: "Token to use to authenticate to Vault.", }, "token_name": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("VAULT_TOKEN_NAME", ""), Description: "Token name to use for creating the Vault child token.", }, "skip_child_token": { - Type: schema.TypeBool, - Optional: true, - DefaultFunc: schema.EnvDefaultFunc("TERRAFORM_VAULT_SKIP_CHILD_TOKEN", false), - + Type: schema.TypeBool, + Optional: true, // Setting to true will cause max_lease_ttl_seconds and token_name to be ignored (not used). // Note that this is strongly discouraged due to the potential of exposing sensitive secret data. Description: "Set this to true to prevent the creation of ephemeral child token used by this provider.", @@ -102,102 +101,59 @@ func NewProvider( consts.FieldCACertFile: { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc(api.EnvVaultCACert, ""), Description: "Path to a CA certificate file to validate the server's certificate.", }, consts.FieldCACertDir: { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc(api.EnvVaultCAPath, ""), Description: "Path to directory containing CA certificate files to validate the server's certificate.", }, - consts.FieldClientAuth: { - Type: schema.TypeList, - Optional: true, - Description: "Client authentication credentials.", - MaxItems: 1, - Deprecated: fmt.Sprintf("Use %s instead", consts.FieldAuthLoginCert), - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - consts.FieldCertFile: { - Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc(api.EnvVaultClientCert, ""), - Description: "Path to a file containing the client certificate.", - }, - consts.FieldKeyFile: { - Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc(api.EnvVaultClientKey, ""), - Description: "Path to a file containing the private key that the certificate was issued for.", - }, - }, - }, - }, consts.FieldSkipTLSVerify: { Type: schema.TypeBool, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("VAULT_SKIP_VERIFY", false), Description: "Set this to true only if the target Vault server is an insecure development instance.", }, consts.FieldTLSServerName: { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc(api.EnvVaultTLSServerName, ""), Description: "Name to use as the SNI host when connecting via TLS.", }, "max_lease_ttl_seconds": { - Type: schema.TypeInt, - Optional: true, - - // Default is 20min, which is intended to be enough time for - // a reasonable Terraform run can complete but not - // significantly longer, so that any leases are revoked shortly - // after Terraform has finished running. - DefaultFunc: schema.EnvDefaultFunc("TERRAFORM_VAULT_MAX_TTL", 1200), + Type: schema.TypeInt, + Optional: true, Description: "Maximum TTL for secret leases requested by this provider.", }, "max_retries": { Type: schema.TypeInt, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("VAULT_MAX_RETRIES", DefaultMaxHTTPRetries), Description: "Maximum number of retries when a 5xx error code is encountered.", }, "max_retries_ccc": { Type: schema.TypeInt, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("VAULT_MAX_RETRIES_CCC", DefaultMaxHTTPRetriesCCC), Description: "Maximum number of retries for Client Controlled Consistency related operations", }, consts.FieldNamespace: { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("VAULT_NAMESPACE", ""), Description: "The namespace to use. Available only for Vault Enterprise.", }, - consts.FieldSetNamespaceFromToken: { - Type: schema.TypeBool, - Optional: true, - Default: true, - Description: "In the case where the Vault token is for a specific namespace " + - "and the provider namespace is not configured, use the token namespace " + - "as the root namespace for all resources.", - }, "headers": { Type: schema.TypeList, Optional: true, - Sensitive: true, Description: "The headers to send with each Vault request.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, Required: true, + Sensitive: true, Description: "The header name", }, "value": { Type: schema.TypeString, Required: true, + Sensitive: true, Description: "The header value", }, }, @@ -206,7 +162,6 @@ func NewProvider( consts.FieldSkipGetVaultVersion: { Type: schema.TypeBool, Optional: true, - Default: false, Description: "Skip the dynamic fetching of the Vault server version.", }, consts.FieldVaultVersionOverride: { @@ -216,6 +171,13 @@ func NewProvider( "which is normally determined dynamically from the target Vault server", ValidateDiagFunc: ValidateDiagSemVer, }, + consts.FieldSetNamespaceFromToken: { + Type: schema.TypeBool, + Optional: true, + Description: "In the case where the Vault token is for a specific namespace " + + "and the provider namespace is not configured, use the token namespace " + + "as the root namespace for all resources.", + }, }, ConfigureFunc: NewProviderMeta, DataSourcesMap: dataSourcesMap, @@ -224,6 +186,11 @@ func NewProvider( MustAddAuthLoginSchema(r.Schema) + // Set the provider Meta (instance data) here. + // It will be overwritten by the result of the call to ConfigureFunc, + // but can be used pre-configuration by other (non-primary) provider servers. + r.SetMeta(&ProviderMeta{}) + return r } diff --git a/internal/provider/validators.go b/internal/provider/validators.go index 15e23212c..986562433 100644 --- a/internal/provider/validators.go +++ b/internal/provider/validators.go @@ -22,7 +22,7 @@ import ( var ( regexpPathLeading = regexp.MustCompile(fmt.Sprintf(`^%s`, consts.PathDelim)) regexpPathTrailing = regexp.MustCompile(fmt.Sprintf(`%s$`, consts.PathDelim)) - regexpPath = regexp.MustCompile(fmt.Sprintf(`%s|%s`, regexpPathLeading, regexpPathTrailing)) + RegexpPath = regexp.MustCompile(fmt.Sprintf(`%s|%s`, regexpPathLeading, regexpPathTrailing)) regexpUUID = regexp.MustCompile("^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$") ) @@ -63,7 +63,7 @@ func ValidateNoTrailingSlash(i interface{}, k string) ([]string, []error) { func ValidateNoLeadingTrailingSlashes(i interface{}, k string) ([]string, []error) { var errs []error - if err := validatePath(regexpPath, i, k); err != nil { + if err := validatePath(RegexpPath, i, k); err != nil { errs = append(errs, err) } @@ -71,7 +71,7 @@ func ValidateNoLeadingTrailingSlashes(i interface{}, k string) ([]string, []erro } func ValidateDiagPath(i interface{}, path cty.Path) diag.Diagnostics { - return validateDiagPath(regexpPath, i, path) + return validateDiagPath(RegexpPath, i, path) } func validateDiagPath(r *regexp.Regexp, i interface{}, path cty.Path) diag.Diagnostics { diff --git a/internal/providertest/providertest.go b/internal/providertest/providertest.go new file mode 100644 index 000000000..61a17dd09 --- /dev/null +++ b/internal/providertest/providertest.go @@ -0,0 +1,40 @@ +package providertest + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-provider-vault/vault" +) + +// ProtoV5ProviderFactories is a static map containing only the main provider instance +var ( + ProtoV5ProviderFactories map[string]func() (tfprotov5.ProviderServer, error) = protoV5ProviderFactoriesInit(context.Background(), "vault") +) + +// testAccProtoV5ProviderFactories will return a map of provider servers +// suitable for use as a resource.TestStep.ProtoV5ProviderFactories. +// +// When multiplexing providers, the schema and configuration handling must +// exactly match between all underlying providers of the mux server. Mismatched +// schemas will result in a runtime error. +// see: https://developer.hashicorp.com/terraform/plugin/framework/migrating/mux +// +// Any tests that use this function will serve as a smoketest to verify the +// provider schemas match 1-1 so that we may catch runtime errors. +func protoV5ProviderFactoriesInit(ctx context.Context, providerNames ...string) map[string]func() (tfprotov5.ProviderServer, error) { + factories := make(map[string]func() (tfprotov5.ProviderServer, error), len(providerNames)) + + for _, name := range providerNames { + factories[name] = func() (tfprotov5.ProviderServer, error) { + providerServerFactory, _, err := vault.ProtoV5ProviderServerFactory(ctx) + if err != nil { + return nil, err + } + + return providerServerFactory(), nil + } + } + + return factories +} diff --git a/internal/vault/auth/README.md b/internal/vault/auth/README.md new file mode 100644 index 000000000..114721f65 --- /dev/null +++ b/internal/vault/auth/README.md @@ -0,0 +1,3 @@ +# Vault Auth Methods + +This package contains Vault [auth method API](https://developer.hashicorp.com/vault/api-docs/auth) resources and datasources. diff --git a/internal/vault/secrets/README.md b/internal/vault/secrets/README.md new file mode 100644 index 000000000..071634415 --- /dev/null +++ b/internal/vault/secrets/README.md @@ -0,0 +1,3 @@ +# Vault Secrets Engine + +This package contains Vault [secrets engine API](https://developer.hashicorp.com/vault/api-docs/secret) resources and datasources. diff --git a/internal/vault/sys/README.md b/internal/vault/sys/README.md new file mode 100644 index 000000000..c77c76b6f --- /dev/null +++ b/internal/vault/sys/README.md @@ -0,0 +1,3 @@ +# Vault System Backend + +This package contains Vault [system backend API](https://developer.hashicorp.com/vault/api-docs/system) resources and datasources. diff --git a/internal/vault/sys/password_policy.go b/internal/vault/sys/password_policy.go new file mode 100644 index 000000000..e66172580 --- /dev/null +++ b/internal/vault/sys/password_policy.go @@ -0,0 +1,266 @@ +package sys + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-provider-vault/internal/framework/base" + "github.com/hashicorp/terraform-provider-vault/internal/framework/client" + "github.com/hashicorp/terraform-provider-vault/internal/framework/model" +) + +// Ensure the implementation satisfies the resource.ResourceWithConfigure interface +var _ resource.ResourceWithConfigure = &PasswordPolicyResource{} + +// NewPasswordPolicyResource returns the implementation for this resource to be +// imported by the Terraform Plugin Framework provider +func NewPasswordPolicyResource() resource.Resource { + return &PasswordPolicyResource{} +} + +// PasswordPolicyResource implements the methods that define this resource +type PasswordPolicyResource struct { + base.ResourceWithConfigure + base.WithImportByID +} + +// PasswordPolicyModel describes the Terraform resource data model to match the +// resource schema. +type PasswordPolicyModel struct { + // common fields to all migrated resources + ID types.String `tfsdk:"id"` + + // common fields to all resources + Namespace types.String `tfsdk:"namespace"` + + // fields specific to this resource + Name types.String `tfsdk:"name"` + Policy types.String `tfsdk:"policy"` +} + +// PasswordPolicyAPIModel describes the Vault API data model. +type PasswordPolicyAPIModel struct { + Policy string `json:"policy" mapstructure:"policy"` +} + +// Metadata defines the resource name as it would appear in Terraform configurations +// +// https://developer.hashicorp.com/terraform/plugin/framework/resources#metadata-method +func (r *PasswordPolicyResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_password_policy" +} + +// Schema defines this resource's schema which is the data that is available in +// the resource's configuration, plan, and state +// +// https://developer.hashicorp.com/terraform/plugin/framework/resources#schema-method +func (r *PasswordPolicyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the password policy.", + Required: true, + }, + "policy": schema.StringAttribute{ + Required: true, + MarkdownDescription: "The password policy document", + }, + }, + MarkdownDescription: "Provides a resource to manage Password Policies.", + } + + base.MustAddLegacyBaseSchema(&resp.Schema) +} + +// Create is called during the terraform apply command. +// +// https://developer.hashicorp.com/terraform/plugin/framework/resources/create +func (r *PasswordPolicyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data PasswordPolicyModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + client, err := client.GetClient(ctx, r.Meta(), data.Namespace.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error Configuring Resource Client", err.Error()) + return + } + + vaultRequest := map[string]interface{}{ + "policy": data.Policy.ValueString(), + } + path := r.path(data.Name.ValueString()) + // vault returns a nil response on success + _, err = client.Logical().Write(path, vaultRequest) + if err != nil { + resp.Diagnostics.AddError( + "Unable to Create Resource", + "An unexpected error occurred while attempting to create the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Error: "+err.Error(), + ) + + return + } + + // write the ID to state which is required for backwards compatibility + data.ID = types.StringValue(data.Name.ValueString()) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +// Read is called during the terraform apply, terraform plan, and terraform +// refresh commands. +// +// https://developer.hashicorp.com/terraform/plugin/framework/resources/read +func (r *PasswordPolicyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data PasswordPolicyModel + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + client, err := client.GetClient(ctx, r.Meta(), data.Namespace.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error Configuring Resource Client", err.Error()) + return + } + + // read the name from the id field to support the import command + name := data.ID.ValueString() + path := r.path(name) + policyResp, err := client.Logical().Read(path) + if err != nil { + resp.Diagnostics.AddError( + "Unable to Read Resource from Vault", + "An unexpected error occurred while attempting to read the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Error: "+err.Error(), + ) + + return + } + if policyResp == nil { + resp.Diagnostics.AddError( + "Unable to Read Resource from Vault", + "An unexpected error occurred while attempting to read the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "Vault response was nil", + ) + + return + } + + var readResp PasswordPolicyAPIModel + err = model.ToAPIModel(policyResp.Data, &readResp) + if err != nil { + resp.Diagnostics.AddError("Unable to translate Vault response data", err.Error()) + return + } + + data.Policy = types.StringValue(readResp.Policy) + + // write the name to state to support the import command + data.Name = types.StringValue(name) + + // write the ID to state which is required for backwards compatibility + data.ID = types.StringValue(name) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +// Update is called during the terraform apply command +// +// https://developer.hashicorp.com/terraform/plugin/framework/resources/update +func (r *PasswordPolicyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data PasswordPolicyModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + client, err := client.GetClient(ctx, r.Meta(), data.Namespace.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error Configuring Resource Client", err.Error()) + return + } + + vaultRequest := map[string]interface{}{ + "policy": data.Policy.ValueString(), + } + path := r.path(data.Name.ValueString()) + // vault returns a nil response on success + _, err = client.Logical().Write(path, vaultRequest) + if err != nil { + resp.Diagnostics.AddError( + "Unable to Update Resource", + "An unexpected error occurred while attempting to update the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Error: "+err.Error(), + ) + + return + } + + // write the ID to state which is required for backwards compatibility + data.ID = types.StringValue(data.Name.ValueString()) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +// Delete is called during the terraform apply command +// +// https://developer.hashicorp.com/terraform/plugin/framework/resources/delete +func (r *PasswordPolicyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data PasswordPolicyModel + + // Read Terraform state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + client, err := client.GetClient(ctx, r.Meta(), data.Namespace.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error Configuring Resource Client", err.Error()) + return + } + + path := r.path(data.Name.ValueString()) + + _, err = client.Logical().Delete(path) + if err != nil { + resp.Diagnostics.AddError( + "Unable to Delete Resource", + "An unexpected error occurred while attempting to delete the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Error: "+err.Error(), + ) + + return + } + + // If the logic reaches here, it implicitly succeeded and will remove + // the resource from state if there are no other errors. +} + +func (r *PasswordPolicyResource) path(name string) string { + return fmt.Sprintf("/sys/policies/password/%s", name) +} diff --git a/internal/vault/sys/password_policy_test.go b/internal/vault/sys/password_policy_test.go new file mode 100644 index 000000000..6ffcd51d1 --- /dev/null +++ b/internal/vault/sys/password_policy_test.go @@ -0,0 +1,103 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package sys_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-vault/internal/providertest" + "github.com/hashicorp/terraform-provider-vault/testutil" +) + +func TestAccPasswordPolicy(t *testing.T) { + policyName := acctest.RandomWithPrefix("test-policy") + resourceName := "vault_password_policy.test" + testPolicy := "length = 20\nrule \"charset\" {\n charset = \"abcde\"\n}\n" + testPolicyUpdated := "length = 20\nrule \"charset\" {\n charset = \"abcde\"\n}\nrule \"charset\" {\n charset = \"1234567890\"\nmin-chars = 1\n}\n" + updatedConfig := testAccPasswordPolicyConfig(policyName, testPolicyUpdated) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testutil.TestAccPreCheck(t) }, + ProtoV5ProviderFactories: providertest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccPasswordPolicyConfig(policyName, testPolicy), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", policyName), + resource.TestCheckResourceAttrSet(resourceName, "policy"), + ), + }, + { + Config: updatedConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", policyName), + resource.TestCheckResourceAttrSet(resourceName, "policy"), + ), + }, + testutil.GetImportTestStep(resourceName, false, nil), + }, + }) +} + +func TestAccPasswordPolicyNS(t *testing.T) { + ns := acctest.RandomWithPrefix("ns") + policyName := acctest.RandomWithPrefix("test-policy") + resourceName := "vault_password_policy.test" + testPolicy := "length = 20\nrule \"charset\" {\n charset = \"abcde\"\n}\n" + testPolicyUpdated := "length = 20\nrule \"charset\" {\n charset = \"abcde\"\n}\nrule \"charset\" {\n charset = \"1234567890\"\nmin-chars = 1\n}\n" + updatedConfig := testAccPasswordPolicyConfigNS(ns, policyName, testPolicyUpdated) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testutil.TestAccPreCheck(t) }, + ProtoV5ProviderFactories: providertest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccPasswordPolicyConfigNS(ns, policyName, testPolicy), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "namespace", ns), + resource.TestCheckResourceAttr(resourceName, "name", policyName), + resource.TestCheckResourceAttrSet(resourceName, "policy"), + ), + }, + { + Config: updatedConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "namespace", ns), + resource.TestCheckResourceAttr(resourceName, "name", policyName), + resource.TestCheckResourceAttrSet(resourceName, "policy"), + ), + }, + testutil.GetImportTestStepNS(t, ns, resourceName, updatedConfig), + testutil.GetImportTestStepNSCleanup(t, updatedConfig), + }, + }) +} + +func testAccPasswordPolicyConfig(policyName, policy string) string { + return fmt.Sprintf(` +resource "vault_password_policy" "test" { + name = "%s" + policy = < **Important** Interacting with Vault from Terraform causes any secrets that you read and write to be persisted in both Terraform's state file @@ -160,12 +160,6 @@ variables in order to keep credential information out of the configuration. a limited child token using auth/token/create in order to enforce a short TTL and limit exposure. *[See usage details below.](#generic)* -* `client_auth` - (Optional) A configuration block, described below, that - provides credentials used by Terraform to authenticate with the Vault - server. At present there is little reason to set this, because Terraform - does not support the TLS certificate authentication mechanism. - *Deprecated, use `auth_login_cert` instead. - * `skip_tls_verify` - (Optional) Set this to `true` to disable verification of the Vault server's TLS certificate. This is strongly discouraged except in prototype or development environments, since it exposes the possibility @@ -214,10 +208,6 @@ variables in order to keep credential information out of the configuration. * `use_root_namespace` - (Optional) Authenticate to the root Vault namespace. Conflicts with `namespace`. -* `set_namespace_from_token` -(Optional) Defaults to `true`. In the case where the Vault token is - for a specific namespace and the provider namespace is not configured, use the token namespace - as the root namespace for all resources. - * `skip_get_vault_version` - (Optional) Skip the dynamic fetching of the Vault server version. Set to `true` when the */sys/seal-status* API endpoint is not available. See [vault_version_override](#vault_version_override) for related info @@ -234,14 +224,6 @@ only ever use this option in the case where the server version cannot be dynamic to be sent along with all requests to the Vault server. This block can be specified multiple times. -The `client_auth` configuration block accepts the following arguments: - -* `cert_file` - (Required) Path to a file on local disk that contains the - PEM-encoded certificate to present to the server. - -* `key_file` - (Required) Path to a file on local disk that contains the - PEM-encoded private key for which the authentication certificate was issued. - The `headers` configuration block accepts the following arguments: * `name` - (Required) The name of the header. @@ -741,9 +723,9 @@ provider "vault" { The Vault provider supports managing [Namespaces][namespaces] (a feature of Vault Enterprise), as well as creating resources in those namespaces by utilizing [Provider Aliasing][aliasing]. The `namespace` option in the [provider -block][provider-block] enables the management of resources in the specified -namespace. -In addition, all resources and data sources support specifying their own `namespace`. +block](#provider-arguments) enables the management of resources in the specified +namespace. +In addition, all resources and data sources support specifying their own `namespace`. All resource's `namespace` will be made relative to the `provider`'s configured namespace. ### Importing namespaced resources @@ -966,11 +948,19 @@ default vault_team_policy ``` -## Tutorials +### Token namespaces + +In the case where the Vault token is for a specific namespace and the provider +namespace is not configured, the provider will use the token namespace as the +root namespace for all resources. This behavior can be disabled by setting the +`VAULT_SET_NAMESPACE_FROM_TOKEN ` environment variable to "false". The only +accepted values are "true" and "false". + + +## Tutorials Refer to the [Codify Management of Vault Enterprise Using Terraform](https://learn.hashicorp.com/tutorials/vault/codify-mgmt-enterprise) tutorial for additional examples using Vault namespaces. -[namespaces]: https://www.vaultproject.io/docs/enterprise/namespaces#vault-enterprise-namespaces -[aliasing]: https://www.terraform.io/docs/configuration/providers.html#alias-multiple-provider-configurations -[provider-block]: /docs#provider-arguments +[namespaces]: https://developer.hashicorp.com/vault/docs/enterprise/namespaces#vault-enterprise-namespaces +[aliasing]: https://developer.hashicorp.com/terraform/language/providers/configuration#alias-multiple-provider-configurations