From 6896e1e87be275b6d3e006f11d5164b9e0e9d76e Mon Sep 17 00:00:00 2001 From: Leandro Carneiro <42899277+carnei-ro@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:43:52 -0300 Subject: [PATCH] feat: HCP Vault Secrets provider (#238) Signed-off-by: Leandro Carneiro --- README.md | 33 ++++ go.mod | 23 ++- go.sum | 62 +++++-- .../hcpvaultsecrets/hcpvaultsecrets.go | 160 ++++++++++++++++++ .../hcpvaultsecrets/hcpvaultsecrets_test.go | 120 +++++++++++++ .../hcpvaultsecrets/hcpvaultutils.go | 138 +++++++++++++++ pkg/stringprovider/stringprovider.go | 3 + vals.go | 5 + vals_hcpvaultsecrets_test.go | 78 +++++++++ 9 files changed, 603 insertions(+), 19 deletions(-) create mode 100644 pkg/providers/hcpvaultsecrets/hcpvaultsecrets.go create mode 100644 pkg/providers/hcpvaultsecrets/hcpvaultsecrets_test.go create mode 100644 pkg/providers/hcpvaultsecrets/hcpvaultutils.go create mode 100644 vals_hcpvaultsecrets_test.go diff --git a/README.md b/README.md index 73bc0ff..649a8e6 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ It supports various backends including: - Pulumi State - Kubernetes - Conjur +- HCP Vault Secrets - Use `vals eval -f refs.yaml` to replace all the `ref`s in the file to actual values and secrets. - Use `vals exec -f env.yaml -- ` to populate envvars and execute the command. @@ -222,6 +223,7 @@ Please see the [relevant unit test cases](https://github.com/helmfile/vals/blob/ - [Pulumi State](#pulumi-state) - [Kubernetes](#kubernetes) - [Conjur](#conjur) +- [HCP Vault Secrets](#hcp-vault-secrets) Please see [pkg/providers](https://github.com/helmfile/vals/tree/master/pkg/providers) for the implementations of all the providers. The package names corresponds to the URI schemes. @@ -767,6 +769,37 @@ Example: - `ref+conjur://branch/variable_name` +### HCP Vault Secrets + +This provider retrieves the value of secrets stored in [HCP Vault Secrets](https://developer.hashicorp.com/hcp/docs/vault-secrets). + +It is based on the [HashiCorp Cloud Platform Go SDK](https://github.com/hashicorp/hcp-sdk-go) lib. + +Environment variables: + +- `HCP_CLIENT_ID`: The service principal Client ID for the HashiCorp Cloud Platform. +- `HCP_CLIENT_SECRET`: The service principal Client Secret for the HashiCorp Cloud Platform. +- `HCP_ORGANIZATION_ID`: (Optional) The organization ID for the HashiCorp Cloud Platform. It can be omitted. If "Organization Name" is set, it will be used to fetch the organization ID, otherwise the organization ID will be set to the first organization ID found. +- `HCP_ORGANIZATION_NAME`: (Optional) The organization name for the HashiCorp Cloud Platform to fetch the organization ID. +- `HCP_PROJECT_ID`: (Optional) The project ID for the HashiCorp Cloud Platform. It can be omitted. If "Project Name" is set, it will be used to fetch the project ID, otherwise the project ID will be set to the first project ID found in the provided organization. +- `HCP_PROJECT_NAME`: (Optional) The project name for the HashiCorp Cloud Platform to fetch the project ID. + +Parameters: + +Parameters are optional and can be passed as query parameters in the URI, taking precedence over environment variables. + +- `client_id`: The service principal Client ID for the HashiCorp Cloud Platform. +- `client_secret`: The service principal Client Secret for the HashiCorp Cloud Platform. +- `organization_id`: The organization ID for the HashiCorp Cloud Platform. It can be omitted. If "Organization Name" is set, it will be used to fetch the organization ID, otherwise the organization ID will be set to the first organization ID found. +- `organization_name`: The organization name for the HashiCorp Cloud Platform to fetch the organization ID. +- `project_id`: The project ID for the HashiCorp Cloud Platform. It can be omitted. If "Project Name" is set, it will be used to fetch the project ID, otherwise the project ID will be set to the first project ID found in the provided organization. +- `project_name`: The project name for the HashiCorp Cloud Platform to fetch the project ID. +- `version`: The version digit of the secret to fetch. If omitted or fail to parse, the latest version will be fetched. + +Example: + +`ref+hcpvaultsecrets://APPLICATION_NAME/SECRET_NAME[?client_id=HCP_CLIENT_ID&client_secret=HCP_CLIENT_SECRET&organization_id=HCP_ORGANIZATION_ID&organization_name=HCP_ORGANIZATION_NAME&project_id=HCP_PROJECT_ID&project_name=HCP_PROJECT_NAME&version=2]` + ## Advanced Usages ### Discriminating config and secrets diff --git a/go.mod b/go.mod index 48e1e0f..2a0ba02 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/cyberark/conjur-api-go v0.11.1 github.com/fujiwara/tfstate-lookup v1.1.6 github.com/getsops/sops/v3 v3.8.1 + github.com/go-openapi/runtime v0.26.2 github.com/google/go-cmp v0.6.0 github.com/hashicorp/golang-lru v1.0.2 github.com/hashicorp/vault/api v1.12.0 @@ -28,6 +29,21 @@ require ( k8s.io/client-go v0.29.2 ) +require ( + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/go-openapi/analysis v0.21.5 // indirect + github.com/go-openapi/errors v0.21.0 // indirect + github.com/go-openapi/loads v0.21.3 // indirect + github.com/go-openapi/spec v0.20.12 // indirect + github.com/go-openapi/strfmt v0.21.10 // indirect + github.com/go-openapi/validate v0.22.4 // indirect + github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + go.mongodb.org/mongo-driver v1.13.1 // indirect +) + require ( cloud.google.com/go v0.112.0 // indirect cloud.google.com/go/compute v1.23.4 // indirect @@ -87,9 +103,9 @@ require ( github.com/go-jose/go-jose/v3 v3.0.1 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-openapi/jsonpointer v0.20.1 // indirect + github.com/go-openapi/jsonreference v0.20.3 // indirect + github.com/go-openapi/swag v0.22.5 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect @@ -115,6 +131,7 @@ require ( github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/go-tfe v1.2.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/hcp-sdk-go v0.85.0 github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index 9b80e2d..a098483 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,8 @@ github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGt github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go v1.50.26 h1:tuv8+dje59DBK1Pj65tSCdD36oamBxKYJgbng4bFylc= @@ -143,7 +145,6 @@ github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvA github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyberark/conjur-api-go v0.11.1 h1:vjaMkw0geJsA+ikMM6UDLg4VLFQWKo/B0i9IWlOQ1f0= @@ -163,8 +164,8 @@ github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPa github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -192,12 +193,26 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/analysis v0.21.5 h1:3tHfEBh6Ia8eKc4M7khOGjPOAlWKJ10d877Cr9teujI= +github.com/go-openapi/analysis v0.21.5/go.mod h1:25YcZosX9Lwz2wBsrFrrsL8bmjjXdlyP6zsr2AMy29M= +github.com/go-openapi/errors v0.21.0 h1:FhChC/duCnfoLj1gZ0BgaBmzhJC2SL/sJr8a2vAobSY= +github.com/go-openapi/errors v0.21.0/go.mod h1:jxNTMUxRCKj65yb/okJGEtahVd7uvWnuWfj53bse4ho= +github.com/go-openapi/jsonpointer v0.20.1 h1:MkK4VEIEZMj4wT9PmjaUmGflVBr9nvud4Q4UVFbDoBE= +github.com/go-openapi/jsonpointer v0.20.1/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= +github.com/go-openapi/jsonreference v0.20.3 h1:EjGcjTW8pD1mRis6+w/gmoBdqv5+RbE9B85D1NgDOVQ= +github.com/go-openapi/jsonreference v0.20.3/go.mod h1:FviDZ46i9ivh810gqzFLl5NttD5q3tSlMLqLr6okedM= +github.com/go-openapi/loads v0.21.3 h1:8sSH2FIm/SnbDUGv572md4YqVMFne/a9Eubvcd3anew= +github.com/go-openapi/loads v0.21.3/go.mod h1:Y3aMR24iHbKHppOj91nQ/SHc0cuPbAr4ndY4a02xydc= +github.com/go-openapi/runtime v0.26.2 h1:elWyB9MacRzvIVgAZCBJmqTi7hBzU0hlKD4IvfX0Zl0= +github.com/go-openapi/runtime v0.26.2/go.mod h1:O034jyRZ557uJKzngbMDJXkcKJVzXJiymdSfgejrcRw= +github.com/go-openapi/spec v0.20.12 h1:cgSLbrsmziAP2iais+Vz7kSazwZ8rsUZd6TUzdDgkVI= +github.com/go-openapi/spec v0.20.12/go.mod h1:iSCgnBcwbMW9SfzJb8iYynXvcY6C/QFrI7otzF7xGM4= +github.com/go-openapi/strfmt v0.21.10 h1:JIsly3KXZB/Qf4UzvzJpg4OELH/0ASDQsyk//TTBDDk= +github.com/go-openapi/strfmt v0.21.10/go.mod h1:vNDMwbilnl7xKiO/Ve/8H8Bb2JIInBnH+lqiw6QWgis= +github.com/go-openapi/swag v0.22.5 h1:fVS63IE3M0lsuWRzuom3RLwUMVI2peDH01s6M70ugys= +github.com/go-openapi/swag v0.22.5/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= +github.com/go-openapi/validate v0.22.4 h1:5v3jmMyIPKTR8Lv9syBAIRxG6lY0RqeBPB1LKEijzk8= +github.com/go-openapi/validate v0.22.4/go.mod h1:qm6O8ZIcPVdSY5219468Jv7kBdGvkiZLPOmqnqTUZ2A= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= @@ -230,6 +245,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS 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/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -299,6 +315,8 @@ github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iP github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hcp-sdk-go v0.85.0 h1:RDXpIf4gIGfz6je8Qq0FGg+8kwj7KboQTOxnklWyVJk= +github.com/hashicorp/hcp-sdk-go v0.85.0/go.mod h1:vQ4fzdL1AmhIAbCw+4zmFe5Hbpajj3NvRWkJoVuxmAk= github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d h1:9ARUJJ1VVynB176G1HCwleORqCaXm/Vx0uUi0dL26I0= github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d/go.mod h1:Yog5+CPEM3c99L1CL2CFCYoSzgWm5vTU58idbRUaLik= github.com/hashicorp/vault/api v1.12.0 h1:meCpJSesvzQyao8FCOgk2fGdoADAnbDu2WPJN1lDLJ4= @@ -325,11 +343,9 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -351,6 +367,8 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyex github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= @@ -366,8 +384,11 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= @@ -390,8 +411,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -399,6 +420,8 @@ github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkB github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -428,17 +451,23 @@ github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVK github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zalando/go-keyring v0.2.3-0.20230503081219-17db2e5354bd h1:D+eeEnOlWcMXbwZ5X3oy68nHafBtGcj1jMKFHtVdybY= github.com/zalando/go-keyring v0.2.3-0.20230503081219-17db2e5354bd/go.mod h1:sI3evg9Wvpw3+n4SqplGSJUMwtDeROfD4nsFz4z9PG0= +go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= +go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0 h1:P+/g8GpuJGYbOp2tAdKrIPUX9JO02q8Q0YNlHolpibA= @@ -460,6 +489,7 @@ golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +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.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= diff --git a/pkg/providers/hcpvaultsecrets/hcpvaultsecrets.go b/pkg/providers/hcpvaultsecrets/hcpvaultsecrets.go new file mode 100644 index 0000000..d209549 --- /dev/null +++ b/pkg/providers/hcpvaultsecrets/hcpvaultsecrets.go @@ -0,0 +1,160 @@ +package hcpvaultsecrets + +import ( + "fmt" + "os" + "strconv" + "strings" + + "gopkg.in/yaml.v3" + + "github.com/helmfile/vals/pkg/api" + "github.com/helmfile/vals/pkg/log" +) + +type provider struct { + log *log.Logger + + ClientID string + ClientSecret string + OrganizationID string + OrganizationName string + ProjectID string + ProjectName string + Version string +} + +func New(l *log.Logger, cfg api.StaticConfig) *provider { + p := &provider{ + log: l, + } + + p.ClientID = cfg.String("client_id") + if p.ClientID == "" { + p.ClientID = os.Getenv("HCP_CLIENT_ID") + } + p.ClientSecret = cfg.String("client_secret") + if p.ClientSecret == "" { + p.ClientSecret = os.Getenv("HCP_CLIENT_SECRET") + } + p.OrganizationID = cfg.String("organization_id") + if p.OrganizationID == "" { + p.OrganizationID = os.Getenv("HCP_ORGANIZATION_ID") + } + p.OrganizationName = cfg.String("organization_name") + if p.OrganizationName == "" { + p.OrganizationName = os.Getenv("HCP_ORGANIZATION_NAME") + } + p.ProjectID = cfg.String("project_id") + if p.ProjectID == "" { + p.ProjectID = os.Getenv("HCP_PROJECT_ID") + } + p.ProjectName = cfg.String("project_name") + if p.ProjectName == "" { + p.ProjectName = os.Getenv("HCP_PROJECT_NAME") + } + + if err := parseVersion(cfg.String("version"), p); err != nil { + p.log.Debugf("hcpvaultsecrets: %v. Using latest version.", err) + } + + if p.ClientID == "" || p.ClientSecret == "" { + p.log.Debugf("hcpvaultsecrets: client_id and client_secret are required") + } + + return p +} + +func (p *provider) GetString(key string) (string, error) { + spec, err := parseKey(key) + if err != nil { + return "", err + } + + if p.OrganizationID == "" || p.ProjectID == "" { + rmClient, err := p.resourceManagerClient() + if err != nil { + return "", err + } + if p.OrganizationID == "" { + p.OrganizationID, err = p.getOrganizationID(rmClient) + if err != nil { + return "", err + } + } + if p.ProjectID == "" { + p.ProjectID, err = p.getProjectID(rmClient) + if err != nil { + return "", err + } + } + } + + vsClient, err := p.vaultSecretsClient() + if err != nil { + return "", err + } + + value, err := p.getSecret(vsClient, spec.applicationName, spec.secretName) + if err != nil { + return "", err + } + + return value, nil +} + +type secretSpec struct { + applicationName string + secretName string +} + +func parseKey(key string) (spec secretSpec, err error) { + // key should be in the format / + components := strings.Split(strings.TrimSuffix(key, "/"), "/") + if len(components) != 2 { + err = fmt.Errorf("invalid secret specifier: %q", key) + return + } + + if strings.TrimSpace(components[0]) == "" { + err = fmt.Errorf("missing key application name: %q", key) + return + } + + if strings.TrimSpace(components[1]) == "" { + err = fmt.Errorf("missing secret name: %q", key) + return + } + + spec.applicationName = components[0] + spec.secretName = components[1] + return +} + +func parseVersion(version string, p *provider) error { + if version == "" { + p.Version = "" + return nil + } + v, err := strconv.ParseInt(version, 10, 64) + if err != nil { + p.Version = "" + return fmt.Errorf("failed to parse version: %v", err) + } + p.Version = strconv.FormatInt(v, 10) + return nil +} + +func (p *provider) GetStringMap(key string) (map[string]interface{}, error) { + m := map[string]interface{}{} + yamlStr, err := p.GetString(key) + if err != nil { + return nil, err + } + + err = yaml.Unmarshal([]byte(yamlStr), &m) + if err != nil { + return nil, fmt.Errorf("error while parsing secret for key %q as yaml: %v", key, err) + } + return m, nil +} diff --git a/pkg/providers/hcpvaultsecrets/hcpvaultsecrets_test.go b/pkg/providers/hcpvaultsecrets/hcpvaultsecrets_test.go new file mode 100644 index 0000000..dc6cc55 --- /dev/null +++ b/pkg/providers/hcpvaultsecrets/hcpvaultsecrets_test.go @@ -0,0 +1,120 @@ +package hcpvaultsecrets + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func Test_parseKey(t *testing.T) { + testcases := []struct { + key string + want secretSpec + wantErr string + }{ + { + key: "test-vault/a-secret", + want: secretSpec{"test-vault", "a-secret"}, + wantErr: "", + }, + { + // strips trailing slash + key: "test-vault/a-secret/", + want: secretSpec{"test-vault", "a-secret"}, + wantErr: "", + }, + { + // illegal key + key: "too-short/", + want: secretSpec{}, + wantErr: `invalid secret specifier: "too-short/"`, + }, + { + // illegal key + key: "to/many/key/components", + want: secretSpec{}, + wantErr: `invalid secret specifier: "to/many/key/components"`, + }, + { + // missing application name + key: "/secret", + want: secretSpec{}, + wantErr: `missing key application name: "/secret"`, + }, + { + // missing secret name + key: "test-vault/ /", + want: secretSpec{}, + wantErr: `missing secret name: "test-vault/ /"`, + }, + } + + for i := range testcases { + tc := testcases[i] + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + got, err := parseKey(tc.key) + if err != nil { + if err.Error() != tc.wantErr { + t.Fatalf("unexpected error: want %q, got %q", tc.wantErr, err.Error()) + } + } else { + if tc.wantErr != "" { + t.Fatalf("expected error did not occur: want %q, got none", tc.wantErr) + } + } + + if diff := cmp.Diff(tc.want, got, cmp.AllowUnexported(secretSpec{})); diff != "" { + t.Errorf("unexpected result: -(want), +(got)\n%s", diff) + } + }) + } +} + +func Test_parseVersion(t *testing.T) { + testcases := []struct { + version string + provider *provider + want string + wantErr string + }{ + { + version: "3", + want: "3", + provider: &provider{}, + wantErr: "", + }, + { + version: "v3", + provider: &provider{}, + want: "", + wantErr: "failed to parse version: strconv.ParseInt: parsing \"v3\": invalid syntax", + }, + { + version: "", + provider: &provider{}, + want: "", + wantErr: "", + }, + } + + for i := range testcases { + tc := testcases[i] + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + err := parseVersion(tc.version, tc.provider) + if err != nil { + if err.Error() != tc.wantErr { + t.Fatalf("unexpected error: want %q, got %q", tc.wantErr, err.Error()) + } + } else { + if tc.wantErr != "" { + t.Fatalf("expected error did not occur: want %q, got none", tc.wantErr) + } + } + + if diff := cmp.Diff(tc.want, tc.provider.Version, cmp.AllowUnexported(secretSpec{})); diff != "" { + t.Errorf("unexpected result: -(want), +(got)\n%s", diff) + } + }) + } +} diff --git a/pkg/providers/hcpvaultsecrets/hcpvaultutils.go b/pkg/providers/hcpvaultsecrets/hcpvaultutils.go new file mode 100644 index 0000000..d501663 --- /dev/null +++ b/pkg/providers/hcpvaultsecrets/hcpvaultutils.go @@ -0,0 +1,138 @@ +package hcpvaultsecrets + +import ( + "fmt" + + httptransport "github.com/go-openapi/runtime/client" + resource_manager "github.com/hashicorp/hcp-sdk-go/clients/cloud-resource-manager/stable/2019-12-10/client" + "github.com/hashicorp/hcp-sdk-go/clients/cloud-resource-manager/stable/2019-12-10/client/project_service" + hcpvaultsecrets "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/stable/2023-06-13/client" + "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/stable/2023-06-13/client/secret_service" + hcpconfig "github.com/hashicorp/hcp-sdk-go/config" + hcpclient "github.com/hashicorp/hcp-sdk-go/httpclient" +) + +func (p *provider) hcpClient() (*httptransport.Runtime, error) { + hcpConfig, err := hcpconfig.NewHCPConfig( + hcpconfig.WithClientCredentials( + p.ClientID, + p.ClientSecret, + ), + ) + if err != nil { + return nil, err + } + + cl, err := hcpclient.New(hcpclient.Config{ + HCPConfig: hcpConfig, + }) + if err != nil { + return nil, err + } + return cl, nil +} + +func (p *provider) resourceManagerClient() (*resource_manager.CloudResourceManager, error) { + cl, err := p.hcpClient() + if err != nil { + return nil, err + } + + rmClient := resource_manager.New(cl, nil) + return rmClient, nil +} + +func (p *provider) vaultSecretsClient() (*hcpvaultsecrets.CloudVaultSecrets, error) { + cl, err := p.hcpClient() + if err != nil { + return nil, err + } + + vsClient := hcpvaultsecrets.New(cl, nil) + return vsClient, nil +} + +func (p *provider) getOrganizationID(rmClient *resource_manager.CloudResourceManager) (string, error) { + organizations, err := rmClient.OrganizationService.OrganizationServiceList(nil, nil) + if err != nil { + return "", err + } + + if p.OrganizationName != "" { + for _, org := range organizations.Payload.Organizations { + if org.Name == p.OrganizationName { + return org.ID, nil + } + } + } + if len(organizations.Payload.Organizations) == 0 { + return "", fmt.Errorf("no organizations found") + } + return organizations.Payload.Organizations[0].ID, nil +} + +func (p *provider) getProjectID(rmClient *resource_manager.CloudResourceManager) (string, error) { + scopeType := "ORGANIZATION" + + projects, err := rmClient.ProjectService.ProjectServiceList( + project_service.NewProjectServiceListParams().WithScopeType(&scopeType).WithScopeID(&p.OrganizationID), + nil, + ) + if err != nil { + return "", err + } + + if len(projects.Payload.Projects) == 0 { + return "", fmt.Errorf("no projects found") + } + + if p.ProjectName != "" { + for _, project := range projects.Payload.Projects { + if project.Name == p.ProjectName { + return project.ID, nil + } + } + } + + return projects.Payload.Projects[0].ID, nil +} + +func (p *provider) openAppSecret(vsClient *hcpvaultsecrets.CloudVaultSecrets, appName string, secretName string) (string, error) { + secrets, err := vsClient.SecretService.OpenAppSecret( + secret_service.NewOpenAppSecretParams(). + WithAppName(appName). + WithSecretName(secretName). + WithLocationOrganizationID(p.OrganizationID). + WithLocationProjectID(p.ProjectID), + nil, + ) + if err != nil { + return "", err + } + + return secrets.Payload.Secret.Version.Value, nil +} + +func (p *provider) openAppSecretVersion(vsClient *hcpvaultsecrets.CloudVaultSecrets, appName string, secretName string) (string, error) { + secrets, err := vsClient.SecretService.OpenAppSecretVersion( + secret_service.NewOpenAppSecretVersionParams(). + WithAppName(appName). + WithSecretName(secretName). + WithLocationOrganizationID(p.OrganizationID). + WithLocationProjectID(p.ProjectID). + WithVersion(p.Version), + nil, + ) + if err != nil { + return "", err + } + + return secrets.Payload.Version.Value, nil +} + +func (p *provider) getSecret(vsClient *hcpvaultsecrets.CloudVaultSecrets, appName string, secretName string) (string, error) { + if p.Version == "" { + return p.openAppSecret(vsClient, appName, secretName) + } + return p.openAppSecretVersion(vsClient, appName, secretName) +} diff --git a/pkg/stringprovider/stringprovider.go b/pkg/stringprovider/stringprovider.go index bd5b0a9..52e8b42 100644 --- a/pkg/stringprovider/stringprovider.go +++ b/pkg/stringprovider/stringprovider.go @@ -14,6 +14,7 @@ import ( "github.com/helmfile/vals/pkg/providers/gcs" "github.com/helmfile/vals/pkg/providers/gitlab" "github.com/helmfile/vals/pkg/providers/gkms" + "github.com/helmfile/vals/pkg/providers/hcpvaultsecrets" "github.com/helmfile/vals/pkg/providers/k8s" "github.com/helmfile/vals/pkg/providers/onepasswordconnect" "github.com/helmfile/vals/pkg/providers/pulumi" @@ -70,6 +71,8 @@ func New(l *log.Logger, provider api.StaticConfig) (api.LazyLoadedStringProvider return k8s.New(l, provider) case "conjur": return conjur.New(l, provider), nil + case "hcpvaultsecrets": + return hcpvaultsecrets.New(l, provider), nil } return nil, fmt.Errorf("failed initializing string provider from config: %v", provider) diff --git a/vals.go b/vals.go index 5d43ac3..23963e8 100644 --- a/vals.go +++ b/vals.go @@ -33,6 +33,7 @@ import ( "github.com/helmfile/vals/pkg/providers/gitlab" "github.com/helmfile/vals/pkg/providers/gkms" "github.com/helmfile/vals/pkg/providers/googlesheets" + "github.com/helmfile/vals/pkg/providers/hcpvaultsecrets" "github.com/helmfile/vals/pkg/providers/k8s" "github.com/helmfile/vals/pkg/providers/onepasswordconnect" "github.com/helmfile/vals/pkg/providers/pulumi" @@ -93,6 +94,7 @@ const ( ProviderGKMS = "gkms" ProviderK8s = "k8s" ProviderConjur = "conjur" + ProviderHCPVaultSecrets = "hcpvaultsecrets" ) var ( @@ -257,6 +259,9 @@ func (r *Runtime) prepare() (*expansion.ExpandRegexMatch, error) { case ProviderConjur: p := conjur.New(r.logger, conf) return p, nil + case ProviderHCPVaultSecrets: + p := hcpvaultsecrets.New(r.logger, conf) + return p, nil } return nil, fmt.Errorf("no provider registered for scheme %q", scheme) } diff --git a/vals_hcpvaultsecrets_test.go b/vals_hcpvaultsecrets_test.go new file mode 100644 index 0000000..a8b1f49 --- /dev/null +++ b/vals_hcpvaultsecrets_test.go @@ -0,0 +1,78 @@ +package vals + +import ( + "fmt" + "os" + "testing" + + config2 "github.com/helmfile/vals/pkg/config" +) + +func TestValues_HCPVaultSecrets_String(t *testing.T) { + // TODO + // Pre-requisite: + // 1. Create a Vault Secrets in HCP + // 2. Configure the HCP Vault Secrets using a Service Principal https://developer.hashicorp.com/vault/tutorials/hcp-vault-secrets-get-started/hcp-vault-secrets-install-cli#configure-the-hcp-vault-secrets-cli + // 3. Set the following environment variables: + // - HCP_CLIENT_ID + // - HCP_CLIENT_SECRET + // 4. Run `vlt config init` + // 5. Create an APP by running `echo y | vlt apps create vals-test` + // 6. Create a simple secret by running `vlt secrets create --app-name vals-test fooKey="myValue"` + + if os.Getenv("SKIP_TESTS") != "" { + t.Skip("Skipping tests") + } + + type testcase struct { + config map[string]interface{} + } + + commonInline := map[string]interface{}{ + "vals-test": "fooKey", + } + + testcases := []testcase{ + { + config: map[string]interface{}{ + "provider": map[string]interface{}{ + "name": "hcpvaultsecrets", + "type": "string", + "path": "vals-test", + }, + "inline": commonInline, + }, + }, + { + config: map[string]interface{}{ + "provider": map[string]interface{}{ + "name": "hcpvaultsecrets", + // implies type=string + "path": "vals-test", + }, + "inline": commonInline, + }, + }, + } + + for i := range testcases { + tc := testcases[i] + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + config := config2.Map(tc.config) + + vals, err := Load(config) + if err != nil { + t.Fatalf("%v", err) + } + + { + expected := "myValue" + key := "vals-test" + actual := vals[key] + if actual != expected { + t.Errorf("unepected value for key %q: expected=%q, got=%q", key, expected, actual) + } + } + }) + } +}