From 5c47be3f8fe0a483f9b1fd5b3b6a8684eb355aa6 Mon Sep 17 00:00:00 2001 From: Googlom <36107508+Googlom@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:29:34 +0500 Subject: [PATCH] refactor(vault): Refactor vault package (#5148) * move to old package * go mod * remove old * refactor done * Update pkg/vault/oidc.go Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com> * commit suggestions Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com> * commit suggestions Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com> * commit suggestions --------- Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com> --- cmd/vaultRotateSecretId.go | 8 +- go.mod | 13 +- go.sum | 46 +- integration/integration_vault_test.go | 33 +- pkg/config/vault.go | 13 +- pkg/vault/client.go | 480 +++++--------------- pkg/vault/helpers.go | 23 + pkg/vault/oidc.go | 54 +-- pkg/vault/oidc_test.go | 6 +- pkg/vault/vault.go | 286 ++++++++++++ pkg/vault/{client_test.go => vault_test.go} | 57 +-- 11 files changed, 541 insertions(+), 478 deletions(-) create mode 100644 pkg/vault/helpers.go create mode 100644 pkg/vault/vault.go rename pkg/vault/{client_test.go => vault_test.go} (89%) diff --git a/cmd/vaultRotateSecretId.go b/cmd/vaultRotateSecretId.go index c7d6602d93..0532350cf4 100644 --- a/cmd/vaultRotateSecretId.go +++ b/cmd/vaultRotateSecretId.go @@ -41,20 +41,22 @@ func (v vaultRotateSecretIDUtilsBundle) UpdateSecretInStore(config *vaultRotateS func vaultRotateSecretId(config vaultRotateSecretIdOptions, telemetryData *telemetry.CustomData) { - vaultConfig := &vault.Config{ + vaultConfig := &vault.ClientConfig{ Config: &api.Config{ Address: config.VaultServerURL, }, Namespace: config.VaultNamespace, + RoleID: GeneralConfig.VaultRoleID, + SecretID: GeneralConfig.VaultRoleSecretID, } - client, err := vault.NewClientWithAppRole(vaultConfig, GeneralConfig.VaultRoleID, GeneralConfig.VaultRoleSecretID) + client, err := vault.NewClient(vaultConfig) if err != nil { log.Entry().WithError(err).Fatal("could not create Vault client") } defer client.MustRevokeToken() utils := vaultRotateSecretIDUtilsBundle{ - Client: &client, + Client: client, config: &config, updateFunc: writeVaultSecretIDToStore, } diff --git a/go.mod b/go.mod index 3fecf3bf95..35fa8e764a 100644 --- a/go.mod +++ b/go.mod @@ -32,8 +32,9 @@ require ( github.com/google/go-containerregistry v0.19.0 github.com/google/go-github/v45 v45.2.0 github.com/google/uuid v1.6.0 - github.com/hashicorp/go-retryablehttp v0.7.2 - github.com/hashicorp/vault/api v1.9.2 + github.com/hashicorp/go-retryablehttp v0.7.7 + github.com/hashicorp/vault/api v1.15.0 + github.com/hashicorp/vault/api/auth/approle v0.8.0 github.com/iancoleman/orderedmap v0.2.0 github.com/imdario/mergo v0.3.15 github.com/influxdata/influxdb-client-go/v2 v2.13.0 @@ -76,7 +77,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ecr v1.32.2 // indirect github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.25.4 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/containerd/errdefs v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect @@ -87,7 +88,7 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-jose/go-jose/v4 v4.0.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/google/gnostic-models v0.6.8 // indirect @@ -152,7 +153,6 @@ require ( github.com/aws/smithy-go v1.20.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/buildpacks/imgutil v0.0.0-20230919143643-4ec9360d5f02 // indirect - github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/containerd v1.7.20 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect @@ -164,7 +164,7 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/fatih/color v1.15.0 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-logr/logr v1.4.1 // indirect @@ -190,7 +190,6 @@ require ( github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect diff --git a/go.sum b/go.sum index c95413fbec..3df2dfff02 100644 --- a/go.sum +++ b/go.sum @@ -196,10 +196,8 @@ github.com/buildpacks/imgutil v0.0.0-20230919143643-4ec9360d5f02/go.mod h1:Ade+4 github.com/buildpacks/lifecycle v0.18.5 h1:lfoUX8jYCUZ2/Tr2AopaRjinqDivkNkcTChzysQTo00= github.com/buildpacks/lifecycle v0.18.5/go.mod h1:Kvuu9IWABPLXc6yHCMtbdmgrGEi7QEiVzi5GGtcAkW0= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= -github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -287,9 +285,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= @@ -321,8 +318,8 @@ github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lK 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= -github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= -github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= +github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -565,14 +562,13 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -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-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= -github.com/hashicorp/go-retryablehttp v0.7.2/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.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= @@ -591,8 +587,10 @@ github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvH github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 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/vault/api v1.9.2 h1:YjkZLJ7K3inKgMZ0wzCU9OHqc+UqMQyXsPXnf3Cl2as= -github.com/hashicorp/vault/api v1.9.2/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= +github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA= +github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8= +github.com/hashicorp/vault/api/auth/approle v0.8.0 h1:FuVtWZ0xD6+wz1x0l5s0b4852RmVXQNEiKhVXt6lfQY= +github.com/hashicorp/vault/api/auth/approle v0.8.0/go.mod h1:NV7O9r5JUtNdVnqVZeMHva81AIdpG0WoIQohNt1VCPM= github.com/heroku/color v0.0.6 h1:UTFFMrmMLFcL3OweqP1lAdp8i1y/9oHqkeHjQ/b/Ny0= github.com/heroku/color v0.0.6/go.mod h1:ZBvOcx7cTF2QKOv4LbmoBtNl5uB17qWxGuzZrsi1wLU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -678,15 +676,11 @@ github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kN github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= @@ -889,7 +883,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -1015,7 +1008,6 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/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= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1102,7 +1094,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1158,7 +1149,6 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1181,10 +1171,7 @@ golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1196,7 +1183,6 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1206,8 +1192,6 @@ golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1222,8 +1206,6 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/integration/integration_vault_test.go b/integration/integration_vault_test.go index 0d29329a68..1be34d67c3 100644 --- a/integration/integration_vault_test.go +++ b/integration/integration_vault_test.go @@ -47,7 +47,7 @@ func TestVaultIntegrationGetSecret(t *testing.T) { assert.NoError(t, err) port, err := vaultContainer.MappedPort(ctx, "8200") host := fmt.Sprintf("http://%s:%s", ip, port.Port()) - config := &vault.Config{Config: &api.Config{Address: host}} + config := &vault.ClientConfig{Config: &api.Config{Address: host}} // setup vault for testing secretData := SecretData{ "key1": "value1", @@ -55,7 +55,7 @@ func TestVaultIntegrationGetSecret(t *testing.T) { } setupVault(t, config, testToken, secretData) - client, err := vault.NewClient(config, testToken) + client, err := vault.NewClientWithToken(config, testToken) assert.NoError(t, err) secret, err := client.GetKvSecret("secret/test") assert.NoError(t, err) @@ -93,14 +93,14 @@ func TestVaultIntegrationWriteSecret(t *testing.T) { assert.NoError(t, err) port, err := vaultContainer.MappedPort(ctx, "8200") host := fmt.Sprintf("http://%s:%s", ip, port.Port()) - config := &vault.Config{Config: &api.Config{Address: host}} + config := &vault.ClientConfig{Config: &api.Config{Address: host}} // setup vault for testing secretData := map[string]string{ "key1": "value1", "key2": "value2", } - client, err := vault.NewClient(config, testToken) + client, err := vault.NewClientWithToken(config, testToken) assert.NoError(t, err) err = client.WriteKvSecret("secret/test", secretData) @@ -159,16 +159,17 @@ func TestVaultIntegrationAppRole(t *testing.T) { assert.NoError(t, err) port, err := vaultContainer.MappedPort(ctx, "8200") host := fmt.Sprintf("http://%s:%s", ip, port.Port()) - config := &vault.Config{Config: &api.Config{Address: host}} + config := &vault.ClientConfig{Config: &api.Config{Address: host}} secretIDMetadata := map[string]interface{}{ "field1": "value1", } roleID, secretID := setupVaultAppRole(t, config, testToken, appRolePath, secretIDMetadata) - + config.RoleID = roleID + config.SecretID = secretID t.Run("Test Vault AppRole login", func(t *testing.T) { - client, err := vault.NewClientWithAppRole(config, roleID, secretID) + client, err := vault.NewClient(config) assert.NoError(t, err) secret, err := client.GetSecret("auth/token/lookup-self") meta := secret.Data["meta"].(SecretData) @@ -178,7 +179,7 @@ func TestVaultIntegrationAppRole(t *testing.T) { }) t.Run("Test Vault AppRoleTTL Fetch", func(t *testing.T) { - client, err := vault.NewClient(config, testToken) + client, err := vault.NewClientWithToken(config, testToken) assert.NoError(t, err) ttl, err := client.GetAppRoleSecretIDTtl(secretID, appRoleName) assert.NoError(t, err) @@ -186,7 +187,7 @@ func TestVaultIntegrationAppRole(t *testing.T) { }) t.Run("Test Vault AppRole Rotation", func(t *testing.T) { - client, err := vault.NewClient(config, testToken) + client, err := vault.NewClientWithToken(config, testToken) assert.NoError(t, err) newSecretID, err := client.GenerateNewAppRoleSecret(secretID, appRoleName) assert.NoError(t, err) @@ -194,7 +195,7 @@ func TestVaultIntegrationAppRole(t *testing.T) { assert.NotEqual(t, secretID, newSecretID) // verify metadata is not broken - client, err = vault.NewClientWithAppRole(config, roleID, newSecretID) + client, err = vault.NewClient(config) assert.NoError(t, err) secret, err := client.GetSecret("auth/token/lookup-self") meta := secret.Data["meta"].(SecretData) @@ -204,7 +205,7 @@ func TestVaultIntegrationAppRole(t *testing.T) { }) t.Run("Test Fetching RoleName from vault", func(t *testing.T) { - client, err := vault.NewClientWithAppRole(config, roleID, secretID) + client, err := vault.NewClient(config) assert.NoError(t, err) fetchedRoleName, err := client.GetAppRoleName() assert.NoError(t, err) @@ -238,16 +239,18 @@ func TestVaultIntegrationTokenRevocation(t *testing.T) { assert.NoError(t, err) port, err := vaultContainer.MappedPort(ctx, "8200") host := fmt.Sprintf("http://%s:%s", ip, port.Port()) - config := &vault.Config{Config: &api.Config{Address: host}} + config := &vault.ClientConfig{Config: &api.Config{Address: host}} secretIDMetadata := map[string]interface{}{ "field1": "value1", } roleID, secretID := setupVaultAppRole(t, config, testToken, appRolePath, secretIDMetadata) + config.RoleID = roleID + config.SecretID = secretID t.Run("Test Revocation works", func(t *testing.T) { - client, err := vault.NewClientWithAppRole(config, roleID, secretID) + client, err := vault.NewClient(config) assert.NoError(t, err) secret, err := client.GetSecret("auth/token/lookup-self") meta := secret.Data["meta"].(SecretData) @@ -263,7 +266,7 @@ func TestVaultIntegrationTokenRevocation(t *testing.T) { }) } -func setupVaultAppRole(t *testing.T, config *vault.Config, token, appRolePath string, metadata map[string]interface{}) (string, string) { +func setupVaultAppRole(t *testing.T, config *vault.ClientConfig, token, appRolePath string, metadata map[string]interface{}) (string, string) { t.Helper() client, err := api.NewClient(config.Config) assert.NoError(t, err) @@ -302,7 +305,7 @@ func setupVaultAppRole(t *testing.T, config *vault.Config, token, appRolePath st return roleID.(string), secretID.(string) } -func setupVault(t *testing.T, config *vault.Config, token string, secret SecretData) { +func setupVault(t *testing.T, config *vault.ClientConfig, token string, secret SecretData) { t.Helper() client, err := api.NewClient(config.Config) assert.NoError(t, err) diff --git a/pkg/config/vault.go b/pkg/config/vault.go index db53761039..c85173d351 100644 --- a/pkg/config/vault.go +++ b/pkg/config/vault.go @@ -87,6 +87,7 @@ var globalVaultClient *vault.Client func GlobalVaultClient() VaultClient { // an interface containing a nil pointer is considered non-nil in Go + // It is nil if Vault is not configured if globalVaultClient == nil { return nil } @@ -122,15 +123,17 @@ func GetVaultClientFromConfig(config map[string]interface{}, creds VaultCredenti namespace = config["vaultNamespace"].(string) log.Entry().Debugf(" with namespace %s", namespace) } - var client vault.Client + var client *vault.Client var err error - clientConfig := &vault.Config{Config: &api.Config{Address: address}, Namespace: namespace} + clientConfig := &vault.ClientConfig{Config: &api.Config{Address: address}, Namespace: namespace} if creds.VaultToken != "" { log.Entry().Debugf(" with Token authentication") - client, err = vault.NewClient(clientConfig, creds.VaultToken) + client, err = vault.NewClientWithToken(clientConfig, creds.VaultToken) } else { log.Entry().Debugf(" with AppRole authentication") - client, err = vault.NewClientWithAppRole(clientConfig, creds.AppRoleID, creds.AppRoleSecretID) + clientConfig.RoleID = creds.AppRoleID + clientConfig.SecretID = creds.AppRoleSecretID + client, err = vault.NewClient(clientConfig) } if err != nil { log.Entry().Info(" failed") @@ -138,7 +141,7 @@ func GetVaultClientFromConfig(config map[string]interface{}, creds VaultCredenti } // Set global vault client for usage in steps - globalVaultClient = &client + globalVaultClient = client log.Entry().Info(" succeeded") return client, nil diff --git a/pkg/vault/client.go b/pkg/vault/client.go index ba1e14e929..659d94c205 100644 --- a/pkg/vault/client.go +++ b/pkg/vault/client.go @@ -2,423 +2,195 @@ package vault import ( "context" - "encoding/json" "fmt" + "github.com/SAP/jenkins-library/pkg/log" + vaultAPI "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/api/auth/approle" + "github.com/pkg/errors" "io" "net/http" - "path" - "strconv" "strings" "time" - - "github.com/SAP/jenkins-library/pkg/log" - "github.com/hashicorp/vault/api" ) // Client handles communication with Vault type Client struct { - lClient logicalClient - config *Config + vaultApiClient *vaultAPI.Client + logical logicalClient + cfg *ClientConfig } -// Config contains the vault client configuration -type Config struct { - *api.Config - AppRoleMountPoint string +type ClientConfig struct { + *vaultAPI.Config Namespace string + AppRoleMountPoint string + RoleID string + SecretID string } // logicalClient interface for mocking type logicalClient interface { - Read(string) (*api.Secret, error) - Write(string, map[string]interface{}) (*api.Secret, error) + Read(string) (*vaultAPI.Secret, error) + Write(string, map[string]interface{}) (*vaultAPI.Secret, error) } -type VaultCredentials struct { - AppRoleID string - AppRoleSecretID string - VaultToken string -} - -// NewClient instantiates a Client and sets the specified token -func NewClient(config *Config, token string) (Client, error) { - if config == nil { - config = &Config{Config: api.DefaultConfig()} - } - client, err := api.NewClient(config.Config) - if err != nil { - return Client{}, err - } - if config.Namespace != "" { - client.SetNamespace(config.Namespace) +func newClient(cfg *ClientConfig) (*Client, error) { + if cfg == nil { + cfg = &ClientConfig{Config: vaultAPI.DefaultConfig()} } - client.SetToken(token) - return Client{client.Logical(), config}, nil -} -// NewClientWithAppRole instantiates a new client and obtains a token via the AppRole auth method -func NewClientWithAppRole(config *Config, roleID, secretID string) (Client, error) { - if config == nil { - config = &Config{Config: api.DefaultConfig()} - } - if config.AppRoleMountPoint == "" { - config.AppRoleMountPoint = "auth/approle" - } - client, err := api.NewClient(config.Config) + var err error + c := &Client{cfg: cfg} + c.vaultApiClient, err = vaultAPI.NewClient(cfg.Config) if err != nil { - return Client{}, err - } - - client.SetMinRetryWait(time.Second * 5) - client.SetMaxRetryWait(time.Second * 90) - client.SetMaxRetries(3) - client.SetCheckRetry(func(ctx context.Context, resp *http.Response, err error) (bool, error) { - if resp != nil { - log.Entry().Debugln("Vault response: ", resp.Status, resp.StatusCode, err) - } else { - log.Entry().Debugln("Vault response: ", err) - } - - isEOF := false - if err != nil && strings.Contains(err.Error(), "EOF") { - log.Entry().Infoln("isEOF is true") - isEOF = true - } - - if err == io.EOF { - log.Entry().Infoln("err = io.EOF is true") - } - - retry, err := api.DefaultRetryPolicy(ctx, resp, err) - - if err != nil || err == io.EOF || isEOF || retry { - log.Entry().Infoln("Retrying vault request...") - return true, nil - } - return false, nil - }) - - if config.Namespace != "" { - client.SetNamespace(config.Namespace) - } - - result, err := client.Logical().Write(path.Join(config.AppRoleMountPoint, "/login"), map[string]interface{}{ - "role_id": roleID, - "secret_id": secretID, - }) - if err != nil { - return Client{}, err + return nil, err } - - authInfo := result.Auth - if authInfo == nil || authInfo.ClientToken == "" { - return Client{}, fmt.Errorf("Could not obtain token from approle with role_id %s", roleID) + c.logical = c.vaultApiClient.Logical() + if cfg.Namespace != "" { + c.vaultApiClient.SetNamespace(cfg.Namespace) } - return NewClient(config, authInfo.ClientToken) + return c, nil } -// GetSecret uses the given path to fetch a secret from vault -func (v Client) GetSecret(path string) (*api.Secret, error) { - path = sanitizePath(path) - c := v.lClient - - secret, err := c.Read(path) +func NewClient(cfg *ClientConfig) (*Client, error) { + c, err := newClient(cfg) if err != nil { - return nil, err + return nil, errors.Wrap(err, "vault client initialization failed") } + applyApiClientRetryConfiguration(c.vaultApiClient) + + initialLoginDone := make(chan struct{}) + go c.startTokenLifecycleManager(initialLoginDone) // this goroutine ends with main goroutine + // wait for initial login or a failure + <-initialLoginDone - return secret, nil + // In case of a failure, the function returns an unauthorized client, which will cause subsequent requests to fail. + return c, nil } -// GetKvSecret reads secret from the KV engine. -// It Automatically transforms the logical path to the HTTP API Path for the corresponding KV Engine version -func (v Client) GetKvSecret(path string) (map[string]string, error) { - path = sanitizePath(path) - mountpath, version, err := v.getKvInfo(path) +func NewClientWithToken(cfg *ClientConfig, token string) (*Client, error) { + c, err := newClient(cfg) if err != nil { - return nil, err - } - if version == 2 { - path = addPrefixToKvPath(path, mountpath, "data") - } else if version != 1 { - return nil, fmt.Errorf("KV Engine in version %d is currently not supported", version) + return nil, errors.Wrap(err, "vault client initialization failed") } - secret, err := v.GetSecret(path) - if secret == nil || err != nil { - return nil, err + c.vaultApiClient.SetToken(token) + return c, nil +} - } - var rawData interface{} - switch version { - case 1: - rawData = secret.Data - case 2: - var ok bool - rawData, ok = secret.Data["data"] - if !ok { - return nil, fmt.Errorf("Missing 'data' field in response: %v", rawData) +func (c *Client) startTokenLifecycleManager(initialLoginDone chan struct{}) { + defer func() { + // make sure to close channel to avoid blocking of the caller + _, open := <-initialLoginDone + if open { + close(initialLoginDone) } - } - - data, ok := rawData.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("Excpected 'data' field to be a map[string]interface{} but got %T instead", rawData) - } - - secretData := make(map[string]string, len(data)) - for k, v := range data { - switch t := v.(type) { - case string: - secretData[k] = t - case int: - secretData[k] = fmt.Sprintf("%d", t) - default: - jsonBytes, err := json.Marshal(t) - if err != nil { - log.Entry().Warnf("failed to parse Vault secret key %q, error: %s", k, err.Error()) - continue - } - - secretData[k] = string(jsonBytes) + }() + + initialLoginSucceed := false + for i := 0; i < c.vaultApiClient.MaxRetries(); i++ { + vaultLoginResp, err := c.login() + if err != nil { + log.Entry().Errorf("unable to authenticate to Vault: %v", err) + continue + } + if !initialLoginSucceed { + initialLoginDone <- struct{}{} + close(initialLoginDone) + initialLoginSucceed = true } - } - return secretData, nil -} -// WriteKvSecret writes secret to kv engine -func (v Client) WriteKvSecret(path string, newSecret map[string]string) error { - oldSecret, err := v.GetKvSecret(path) - if err != nil { - return err - } - secret := make(map[string]interface{}, len(oldSecret)) - for k, v := range oldSecret { - secret[k] = v - } - for k, v := range newSecret { - secret[k] = v - } - path = sanitizePath(path) - mountpath, version, err := v.getKvInfo(path) - if err != nil { - return err - } - if version == 2 { - path = addPrefixToKvPath(path, mountpath, "data") - secret = map[string]interface{}{"data": secret} - } else if version != 1 { - return fmt.Errorf("KV Engine in version %d is currently not supported", version) + tokenErr := c.manageTokenLifecycle(vaultLoginResp) + if tokenErr != nil { + log.Entry().Errorf("unable to start managing token lifecycle: %v", err) + continue + } } - - _, err = v.lClient.Write(path, secret) - return err } -// GenerateNewAppRoleSecret creates a new secret-id -func (v *Client) GenerateNewAppRoleSecret(secretID, appRoleName string) (string, error) { - appRolePath := v.getAppRolePath(appRoleName) - secretIDData, err := v.lookupSecretID(secretID, appRolePath) - if err != nil { - return "", err +// Starts token lifecycle management. Returns only fatal errors as errors, +// otherwise returns nil, so we can attempt login again. +func (c *Client) manageTokenLifecycle(authResp *vaultAPI.Secret) error { + if !authResp.Auth.Renewable { + log.Entry().Debugf("Token is not configured to be renewable. Re-attempting login.") + return nil } - reqPath := sanitizePath(path.Join(appRolePath, "/secret-id")) - - // we preserve metadata which was attached to the secret-id - json, err := json.Marshal(secretIDData["metadata"]) + watcher, err := c.vaultApiClient.NewLifetimeWatcher(&vaultAPI.LifetimeWatcherInput{Secret: authResp}) if err != nil { - return "", err + return fmt.Errorf("unable to initialize new lifetime watcher for renewing auth token: %w", err) } - secret, err := v.lClient.Write(reqPath, map[string]interface{}{ - "metadata": string(json), - }) - if err != nil { - return "", err - } - - if secret == nil || secret.Data == nil { - return "", fmt.Errorf("Could not generate new approle secret-id for approle path %s", reqPath) - } + go watcher.Start() + defer watcher.Stop() - secretIDRaw, ok := secret.Data["secret_id"] - if !ok { - return "", fmt.Errorf("Vault response for path %s did not contain a new secret-id", reqPath) - } + for { + select { + // `DoneCh` will return if renewal fails, or if the remaining lease + // duration is under a built-in threshold and either renewing is not + // extending it or renewing is disabled. In any case, the caller + // needs to attempt to log in again. + case err := <-watcher.DoneCh(): + if err != nil { + log.Entry().Printf("Failed to renew Vault token: %v. Re-attempting login.", err) + return nil + } + // This occurs once the token has reached max TTL. + log.Entry().Printf("Token can no longer be renewed. Re-attempting login.") + return nil - newSecretID, ok := secretIDRaw.(string) - if !ok { - return "", fmt.Errorf("New secret-id from approle path %s has an unexpected type %T expected 'string'", reqPath, secretIDRaw) + // Successfully completed renewal + case <-watcher.RenewCh(): + log.Entry().Printf("Vault token successfully renewed") + } } - - return newSecretID, nil } -// GetAppRoleSecretIDTtl returns the remaining time until the given secret-id expires -func (v *Client) GetAppRoleSecretIDTtl(secretID, roleName string) (time.Duration, error) { - appRolePath := v.getAppRolePath(roleName) - data, err := v.lookupSecretID(secretID, appRolePath) - if err != nil { - return 0, err - } - - if data == nil || data["expiration_time"] == nil { - return 0, fmt.Errorf("Could not load secret-id information from path %s", appRolePath) - } - - expiration, ok := data["expiration_time"].(string) - if !ok || expiration == "" { - return 0, fmt.Errorf("Could not handle get expiration time for secret-id from path %s", appRolePath) - } - - expirationDate, err := time.Parse(time.RFC3339, expiration) - +func (c *Client) login() (*vaultAPI.Secret, error) { + appRoleAuth, err := approle.NewAppRoleAuth(c.cfg.RoleID, &approle.SecretID{FromString: c.cfg.SecretID}) if err != nil { - return 0, err - } - - ttl := expirationDate.Sub(time.Now()) - if ttl < 0 { - return 0, nil - } - - return ttl, nil -} - -// RevokeToken revokes the token which is currently used. -// The client can't be used anymore after this function was called. -func (v Client) RevokeToken() error { - _, err := v.lClient.Write("auth/token/revoke-self", map[string]interface{}{}) - return err -} - -// MustRevokeToken same as RevokeToken but the programm is terminated with an error if this fails. -// Should be used in defer statements only. -func (v Client) MustRevokeToken() { - if err := v.RevokeToken(); err != nil { - log.Entry().WithError(err).Fatal("Could not revoke token") + return nil, fmt.Errorf("unable to initialize appRole auth method: %w", err) } -} -// GetAppRoleName returns the AppRole role name which was used to authenticate. -// Returns "" when AppRole authentication wasn't used -func (v *Client) GetAppRoleName() (string, error) { - const lookupPath = "auth/token/lookup-self" - secret, err := v.GetSecret(lookupPath) + authInfo, err := c.vaultApiClient.Auth().Login(context.Background(), appRoleAuth) if err != nil { - return "", err - } - - if secret.Data == nil { - return "", fmt.Errorf("Could not lookup token information: %s", lookupPath) - } - - meta, ok := secret.Data["meta"] - - if !ok { - return "", fmt.Errorf("Token info did not contain metadata %s", lookupPath) - } - - metaMap, ok := meta.(map[string]interface{}) - - if !ok { - return "", fmt.Errorf("Token info field 'meta' is not a map: %s", lookupPath) - } - - roleName := metaMap["role_name"] - - if roleName == nil { - return "", nil + return nil, fmt.Errorf("unable to login to appRole auth method: %w", err) } - - roleNameStr, ok := roleName.(string) - if !ok { - // when approle authentication is not used vault admins can use the role_name field with other type - // so no error in this case - return "", nil - } - - return roleNameStr, nil -} - -// SetAppRoleMountPoint sets the path under which the approle auth backend is mounted -func (v *Client) SetAppRoleMountPoint(appRoleMountpoint string) { - v.config.AppRoleMountPoint = appRoleMountpoint -} - -func (v *Client) getAppRolePath(roleName string) string { - appRoleMountPoint := v.config.AppRoleMountPoint - if appRoleMountPoint == "" { - appRoleMountPoint = "auth/approle" + if authInfo == nil { + return nil, fmt.Errorf("no auth info was returned after login") } - return path.Join(appRoleMountPoint, "role", roleName) -} -func sanitizePath(path string) string { - path = strings.TrimSpace(path) - path = strings.TrimPrefix(path, "/") - path = strings.TrimSuffix(path, "/") - return path + return authInfo, nil } -func addPrefixToKvPath(p, mountPath, apiPrefix string) string { - switch { - case p == mountPath, p == strings.TrimSuffix(mountPath, "/"): - return path.Join(mountPath, apiPrefix) - default: - p = strings.TrimPrefix(p, mountPath) - return path.Join(mountPath, apiPrefix, p) - } -} - -func (v *Client) getKvInfo(path string) (string, int, error) { - secret, err := v.GetSecret("sys/internal/ui/mounts/" + path) - if err != nil { - return "", 0, err - } - - if secret == nil { - return "", 0, fmt.Errorf("Failed to get version and engine mountpoint for path: %s", path) - } - - var mountPath string - if mountPathRaw, ok := secret.Data["path"]; ok { - mountPath = mountPathRaw.(string) - } - - options := secret.Data["options"] - if options == nil { - return mountPath, 1, nil - } - - versionRaw := options.(map[string]interface{})["version"] - if versionRaw == nil { - return mountPath, 1, nil - } +func applyApiClientRetryConfiguration(vaultApiClient *vaultAPI.Client) { + vaultApiClient.SetMinRetryWait(time.Second * 5) + vaultApiClient.SetMaxRetryWait(time.Second * 90) + vaultApiClient.SetMaxRetries(3) + vaultApiClient.SetCheckRetry(func(ctx context.Context, resp *http.Response, err error) (bool, error) { + if resp != nil { + log.Entry().Debugln("Vault response: ", resp.Status, resp.StatusCode, err) + } else { + log.Entry().Debugln("Vault response: ", err) + } - version := versionRaw.(string) - if version == "" { - return mountPath, 1, nil - } + isEOF := false + if err != nil && strings.Contains(err.Error(), "EOF") { + log.Entry().Infoln("isEOF is true") + isEOF = true + } - vNumber, err := strconv.Atoi(version) - if err != nil { - return mountPath, 0, err - } + if err == io.EOF { + log.Entry().Infoln("err = io.EOF is true") + } - return mountPath, vNumber, nil -} + retry, err := vaultAPI.DefaultRetryPolicy(ctx, resp, err) -func (v *Client) lookupSecretID(secretID, appRolePath string) (map[string]interface{}, error) { - reqPath := sanitizePath(path.Join(appRolePath, "/secret-id/lookup")) - secret, err := v.lClient.Write(reqPath, map[string]interface{}{ - "secret_id": secretID, + if err != nil || err == io.EOF || isEOF || retry { + log.Entry().Infoln("Retrying vault request...") + return true, nil + } + return false, nil }) - if err != nil { - return nil, err - } - - return secret.Data, nil } diff --git a/pkg/vault/helpers.go b/pkg/vault/helpers.go new file mode 100644 index 0000000000..1c9db72cbb --- /dev/null +++ b/pkg/vault/helpers.go @@ -0,0 +1,23 @@ +package vault + +import ( + "path" + "strings" +) + +func sanitizePath(path string) string { + path = strings.TrimSpace(path) + path = strings.TrimPrefix(path, "/") + path = strings.TrimSuffix(path, "/") + return path +} + +func addPrefixToKvPath(p, mountPath, apiPrefix string) string { + switch { + case p == mountPath, p == strings.TrimSuffix(mountPath, "/"): + return path.Join(mountPath, apiPrefix) + default: + p = strings.TrimPrefix(p, mountPath) + return path.Join(mountPath, apiPrefix, p) + } +} diff --git a/pkg/vault/oidc.go b/pkg/vault/oidc.go index 493050d04d..972084b59b 100644 --- a/pkg/vault/oidc.go +++ b/pkg/vault/oidc.go @@ -13,39 +13,47 @@ import ( "github.com/pkg/errors" ) -type JwtPayload struct { +type jwtPayload struct { Expire int64 `json:"exp"` } // getOIDCToken returns the generated OIDC token and sets it in the env -func (v Client) getOIDCToken(roleID string) (string, error) { +func (c *Client) getOIDCToken(roleID string) (string, error) { oidcPath := sanitizePath(path.Join("identity/oidc/token/", roleID)) - c := v.lClient - jwt, err := c.Read(oidcPath) + jwt, err := c.logical.Read(oidcPath) if err != nil { return "", err } token := jwt.Data["token"].(string) + if token == "" { + return "", fmt.Errorf("received an empty token") + } + log.RegisterSecret(token) os.Setenv("PIPER_OIDCIdentityToken", token) return token, nil } -// getJWTTokenPayload returns the payload of the JWT token using base64 decoding -func getJWTTokenPayload(token string) ([]byte, error) { +// getJWTPayload returns the payload of the JWT token using base64 decoding +func getJWTPayload(token string) (*jwtPayload, error) { parts := strings.Split(token, ".") - if len(parts) >= 2 { - substr := parts[1] - decodedBytes, err := base64.RawStdEncoding.DecodeString(substr) - if err != nil { - return nil, errors.Wrap(err, "JWT payload couldn't be decoded: %s") - } - return decodedBytes, nil + if len(parts) < 2 { + return nil, fmt.Errorf("not a valid JWT token") + } + + decodedBytes, err := base64.RawStdEncoding.DecodeString(parts[1]) + if err != nil { + return nil, errors.Wrap(err, "JWT payload couldn't be decoded: %s") + } + + var payload jwtPayload + if err = json.Unmarshal(decodedBytes, &payload); err != nil { + return nil, errors.Wrap(err, "JWT unmarshal failed") } - return nil, fmt.Errorf("Not a valid JWT token") + return &payload, nil } func oidcTokenIsValid(token string) bool { @@ -53,34 +61,28 @@ func oidcTokenIsValid(token string) bool { return false } - payload, err := getJWTTokenPayload(token) + jwtTokenPayload, err := getJWTPayload(token) if err != nil { log.Entry().Debugf("OIDC token couldn't be validated: %s", err) return false } - var jwtPayload JwtPayload - err = json.Unmarshal(payload, &jwtPayload) - if err != nil { - log.Entry().Debugf("OIDC token couldn't be validated: %s", err) - return false - } - - expiryTime := time.Unix(jwtPayload.Expire, 0) + expiryTime := time.Unix(jwtTokenPayload.Expire, 0) currentTime := time.Now() return expiryTime.After(currentTime) } // GetOIDCTokenByValidation returns the token if token is expired then get a new token else return old token -func (v Client) GetOIDCTokenByValidation(roleID string) (string, error) { +func (c *Client) GetOIDCTokenByValidation(roleID string) (string, error) { token := os.Getenv("PIPER_OIDCIdentityToken") if oidcTokenIsValid(token) { return token, nil } - token, err := v.getOIDCToken(roleID) - if token == "" || err != nil { + log.Entry().Debug("obtaining new OIDC token") + token, err := c.getOIDCToken(roleID) + if err != nil { return "", err } diff --git a/pkg/vault/oidc_test.go b/pkg/vault/oidc_test.go index 2e1a973305..7547a54ee8 100644 --- a/pkg/vault/oidc_test.go +++ b/pkg/vault/oidc_test.go @@ -28,7 +28,7 @@ func TestOIDC(t *testing.T) { // init vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Read", oidcPath).Return(mockJwt, nil) // run @@ -50,7 +50,7 @@ func TestOIDC(t *testing.T) { t.Setenv("PIPER_OIDCIdentityToken", token) vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Read", oidcPath).Return(mockJwt, nil) // run @@ -72,7 +72,7 @@ func TestOIDC(t *testing.T) { t.Setenv("PIPER_OIDCIdentityToken", token) vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Read", oidcPath).Return(mockJwt, nil) // run diff --git a/pkg/vault/vault.go b/pkg/vault/vault.go new file mode 100644 index 0000000000..5943b31c19 --- /dev/null +++ b/pkg/vault/vault.go @@ -0,0 +1,286 @@ +package vault + +import ( + "encoding/json" + "fmt" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/hashicorp/vault/api" + "path" + "strconv" + "time" +) + +// GetSecret uses the given path to fetch a secret from vault +func (c *Client) GetSecret(path string) (*api.Secret, error) { + return c.logical.Read(sanitizePath(path)) +} + +// GetKvSecret reads secret from the KV engine. +// It Automatically transforms the logical path to the HTTP API Path for the corresponding KV Engine version +func (c *Client) GetKvSecret(path string) (map[string]string, error) { + path = sanitizePath(path) + mountPath, version, err := c.getKvInfo(path) + if err != nil { + return nil, err + } + if version == 2 { + path = addPrefixToKvPath(path, mountPath, "data") + } else if version != 1 { + return nil, fmt.Errorf("KV Engine in version %d is currently not supported", version) + } + + secret, err := c.GetSecret(path) + if secret == nil || err != nil { + return nil, err + + } + var rawData interface{} + switch version { + case 1: + rawData = secret.Data + case 2: + var ok bool + rawData, ok = secret.Data["data"] + if !ok { + return nil, fmt.Errorf("missing 'data' field in response: %v", rawData) + } + } + + data, ok := rawData.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("excpected 'data' field to be a map[string]interface{} but got %T instead", rawData) + } + + secretData := make(map[string]string, len(data)) + for k, v := range data { + switch t := v.(type) { + case string: + secretData[k] = t + case int: + secretData[k] = fmt.Sprintf("%d", t) + default: + jsonBytes, err := json.Marshal(t) + if err != nil { + log.Entry().Warnf("failed to parse Vault secret key %q, error: %s", k, err.Error()) + continue + } + + secretData[k] = string(jsonBytes) + } + } + return secretData, nil +} + +// WriteKvSecret writes secret to kv engine +func (c *Client) WriteKvSecret(path string, newSecret map[string]string) error { + oldSecret, err := c.GetKvSecret(path) + if err != nil { + return err + } + secret := make(map[string]interface{}, len(oldSecret)) + for k, v := range oldSecret { + secret[k] = v + } + for k, v := range newSecret { + secret[k] = v + } + path = sanitizePath(path) + mountPath, version, err := c.getKvInfo(path) + if err != nil { + return err + } + if version == 2 { + path = addPrefixToKvPath(path, mountPath, "data") + secret = map[string]interface{}{"data": secret} + } else if version != 1 { + return fmt.Errorf("KV Engine in version %d is currently not supported", version) + } + + _, err = c.logical.Write(path, secret) + return err +} + +// GenerateNewAppRoleSecret creates a new secret-id +func (c *Client) GenerateNewAppRoleSecret(secretID, appRoleName string) (string, error) { + appRolePath := c.getAppRolePath(appRoleName) + secretIDData, err := c.lookupSecretID(secretID, appRolePath) + if err != nil { + return "", err + } + + reqPath := sanitizePath(path.Join(appRolePath, "/secret-id")) + + // we preserve metadata which was attached to the secret-id + jsonBytes, err := json.Marshal(secretIDData["metadata"]) + if err != nil { + return "", err + } + + secret, err := c.logical.Write(reqPath, map[string]interface{}{ + "metadata": string(jsonBytes), + }) + if err != nil { + return "", err + } + + if secret == nil || secret.Data == nil { + return "", fmt.Errorf("could not generate new approle secret-id for approle path %s", reqPath) + } + + secretIDRaw, ok := secret.Data["secret_id"] + if !ok { + return "", fmt.Errorf("Vault response for path %s did not contain a new secret-id", reqPath) + } + + newSecretID, ok := secretIDRaw.(string) + if !ok { + return "", fmt.Errorf("new secret-id from approle path %s has an unexpected type %T expected 'string'", reqPath, secretIDRaw) + } + + return newSecretID, nil +} + +// GetAppRoleSecretIDTtl returns the remaining time until the given secret-id expires +func (c *Client) GetAppRoleSecretIDTtl(secretID, roleName string) (time.Duration, error) { + appRolePath := c.getAppRolePath(roleName) + data, err := c.lookupSecretID(secretID, appRolePath) + if err != nil { + return 0, err + } + + if data == nil || data["expiration_time"] == nil { + return 0, fmt.Errorf("could not load secret-id information from path %s", appRolePath) + } + + expiration, ok := data["expiration_time"].(string) + if !ok || expiration == "" { + return 0, fmt.Errorf("could not handle get expiration time for secret-id from path %s", appRolePath) + } + + expirationDate, err := time.Parse(time.RFC3339, expiration) + + if err != nil { + return 0, err + } + + ttl := expirationDate.Sub(time.Now()) + if ttl < 0 { + return 0, nil + } + + return ttl, nil +} + +// RevokeToken revokes the token which is currently used. +// The client can't be used anymore after this function was called. +func (c *Client) RevokeToken() error { + _, err := c.logical.Write("auth/token/revoke-self", map[string]interface{}{}) + return err +} + +// MustRevokeToken same as RevokeToken but the program is terminated with an error if this fails. +// Should be used in defer statements only. +func (c *Client) MustRevokeToken() { + if err := c.RevokeToken(); err != nil { + log.Entry().WithError(err).Fatal("Could not revoke token") + } +} + +// GetAppRoleName returns the AppRole role name which was used to authenticate. +// Returns "" when AppRole authentication wasn't used +func (c *Client) GetAppRoleName() (string, error) { + const lookupPath = "auth/token/lookup-self" + secret, err := c.GetSecret(lookupPath) + if err != nil { + return "", err + } + + if secret.Data == nil { + return "", fmt.Errorf("could not lookup token information: %s", lookupPath) + } + + meta, ok := secret.Data["meta"] + + if !ok { + return "", fmt.Errorf("token info did not contain metadata %s", lookupPath) + } + + metaMap, ok := meta.(map[string]interface{}) + + if !ok { + return "", fmt.Errorf("token info field 'meta' is not a map: %s", lookupPath) + } + + roleName := metaMap["role_name"] + + if roleName == nil { + return "", nil + } + + roleNameStr, ok := roleName.(string) + if !ok { + // when AppRole authentication is not used vault admins can use the role_name field with other type + // so no error in this case + return "", nil + } + + return roleNameStr, nil +} + +func (c *Client) getAppRolePath(roleName string) string { + appRoleMountPoint := c.cfg.AppRoleMountPoint + if appRoleMountPoint == "" { + appRoleMountPoint = "auth/approle" + } + return path.Join(appRoleMountPoint, "role", roleName) +} + +func (c *Client) getKvInfo(path string) (string, int, error) { + secret, err := c.GetSecret("sys/internal/ui/mounts/" + path) + if err != nil { + return "", 0, err + } + + if secret == nil { + return "", 0, fmt.Errorf("failed to get version and engine mountpoint for path: %s", path) + } + + var mountPath string + if mountPathRaw, ok := secret.Data["path"]; ok { + mountPath = mountPathRaw.(string) + } + + options := secret.Data["options"] + if options == nil { + return mountPath, 1, nil + } + + versionRaw := options.(map[string]interface{})["version"] + if versionRaw == nil { + return mountPath, 1, nil + } + + version := versionRaw.(string) + if version == "" { + return mountPath, 1, nil + } + + vNumber, err := strconv.Atoi(version) + if err != nil { + return mountPath, 0, err + } + + return mountPath, vNumber, nil +} + +func (c *Client) lookupSecretID(secretID, appRolePath string) (map[string]interface{}, error) { + reqPath := sanitizePath(path.Join(appRolePath, "/secret-id/lookup")) + secret, err := c.logical.Write(reqPath, map[string]interface{}{ + "secret_id": secretID, + }) + if err != nil { + return nil, err + } + + return secret.Data, nil +} diff --git a/pkg/vault/client_test.go b/pkg/vault/vault_test.go similarity index 89% rename from pkg/vault/client_test.go rename to pkg/vault/vault_test.go index 90a86b35bb..a71bfe9ea0 100644 --- a/pkg/vault/client_test.go +++ b/pkg/vault/vault_test.go @@ -30,7 +30,7 @@ const ( func TestGetKV2Secret(t *testing.T) { t.Run("Test missing secret", func(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} setupMockKvV2(vaultMock) vaultMock.On("Read", "secret/data/notexist").Return(nil, nil) secret, err := client.GetKvSecret("secret/notexist") @@ -45,7 +45,7 @@ func TestGetKV2Secret(t *testing.T) { t.Run("Getting secret from KV engine (v2)", func(t *testing.T) { vaultMock := &mocks.VaultMock{} setupMockKvV2(vaultMock) - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Read", secretAPIPath).Return(kv2Secret(SecretData{"key1": "value1", "key2": map[string]any{"subkey1": "subvalue2"}, "key3": 3, "key4": []string{"foo", "bar"}}), nil) secret, err := client.GetKvSecret(secretName) assert.NoError(t, err, "Expect GetKvSecret to succeed") @@ -58,7 +58,7 @@ func TestGetKV2Secret(t *testing.T) { t.Run("error is thrown when data field is missing", func(t *testing.T) { vaultMock := &mocks.VaultMock{} setupMockKvV2(vaultMock) - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Read", secretAPIPath).Return(kv1Secret(SecretData{"key1": "value1"}), nil) secret, err := client.GetKvSecret(secretName) assert.Error(t, err, "Expected to fail since 'data' field is missing") @@ -74,7 +74,7 @@ func TestGetKV1Secret(t *testing.T) { t.Run("Test missing secret", func(t *testing.T) { vaultMock := &mocks.VaultMock{} setupMockKvV1(vaultMock) - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Read", mock.AnythingOfType("string")).Return(nil, nil) secret, err := client.GetKvSecret("secret/notexist") @@ -85,7 +85,7 @@ func TestGetKV1Secret(t *testing.T) { t.Run("Test parsing KV1 secrets", func(t *testing.T) { vaultMock := &mocks.VaultMock{} setupMockKvV1(vaultMock) - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Read", secretName).Return(kv1Secret(SecretData{"key1": "value1", "key2": 5}), nil) secret, err := client.GetKvSecret(secretName) @@ -103,7 +103,7 @@ func TestSecretIDGeneration(t *testing.T) { t.Run("Test generating new secret-id", func(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} now := time.Now() expiry := now.Add(5 * time.Hour).Format(time.RFC3339) metadata := map[string]interface{}{ @@ -128,7 +128,7 @@ func TestSecretIDGeneration(t *testing.T) { t.Run("Test with no secret-id returned", func(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} now := time.Now() expiry := now.Add(5 * time.Hour).Format(time.RFC3339) metadata := map[string]interface{}{ @@ -151,7 +151,7 @@ func TestSecretIDGeneration(t *testing.T) { t.Run("Test with no new secret-id returned", func(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} now := time.Now() expiry := now.Add(5 * time.Hour).Format(time.RFC3339) metadata := map[string]interface{}{ @@ -168,7 +168,7 @@ func TestSecretIDGeneration(t *testing.T) { vaultMock.On("Write", path.Join(appRolePath, "/secret-id"), mapWith("metadata", string(metadataJSON))).Return(kv1Secret(nil), nil) newSecretID, err := client.GenerateNewAppRoleSecret(secretID, appRoleName) - assert.EqualError(t, err, fmt.Sprintf("Could not generate new approle secret-id for approle path %s", path.Join(appRolePath, "secret-id"))) + assert.EqualError(t, err, fmt.Sprintf("could not generate new approle secret-id for approle path %s", path.Join(appRolePath, "secret-id"))) assert.Equal(t, newSecretID, "") }) } @@ -181,7 +181,7 @@ func TestSecretIDTtl(t *testing.T) { t.Run("Test fetching secreID TTL", func(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} now := time.Now() expiry := now.Add(5 * time.Hour).Format(time.RFC3339) vaultMock.On("Write", path.Join(appRolePath, "secret-id/lookup"), mapWith("secret_id", secretID)).Return(kv1Secret(SecretData{ @@ -195,16 +195,16 @@ func TestSecretIDTtl(t *testing.T) { t.Run("Test with no expiration time", func(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Write", path.Join(appRolePath, "secret-id/lookup"), mapWith("secret_id", secretID)).Return(kv1Secret(SecretData{}), nil) ttl, err := client.GetAppRoleSecretIDTtl(secretID, appRoleName) - assert.EqualError(t, err, fmt.Sprintf("Could not load secret-id information from path %s", appRolePath)) + assert.EqualError(t, err, fmt.Sprintf("could not load secret-id information from path %s", appRolePath)) assert.Equal(t, time.Duration(0), ttl) }) t.Run("Test with wrong date format", func(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Write", path.Join(appRolePath, "secret-id/lookup"), mapWith("secret_id", secretID)).Return(kv1Secret(SecretData{ "expiration_time": time.Now().String(), }), nil) @@ -215,7 +215,7 @@ func TestSecretIDTtl(t *testing.T) { t.Run("Test with expired secret-id", func(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} now := time.Now() expiry := now.Add(-5 * time.Hour).Format(time.RFC3339) vaultMock.On("Write", path.Join(appRolePath, "secret-id/lookup"), mapWith("secret_id", secretID)).Return(kv1Secret(SecretData{ @@ -234,7 +234,7 @@ func TestGetAppRoleName(t *testing.T) { t.Run("Test that correct role name is returned", func(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Read", "auth/token/lookup-self").Return(kv1Secret(SecretData{ "meta": SecretData{ "role_name": "test", @@ -248,27 +248,27 @@ func TestGetAppRoleName(t *testing.T) { t.Run("Test without secret data", func(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Read", "auth/token/lookup-self").Return(kv1Secret(nil), nil) appRoleName, err := client.GetAppRoleName() - assert.EqualError(t, err, "Could not lookup token information: auth/token/lookup-self") + assert.EqualError(t, err, "could not lookup token information: auth/token/lookup-self") assert.Empty(t, appRoleName) }) t.Run("Test without metadata data", func(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Read", "auth/token/lookup-self").Return(kv1Secret(SecretData{}), nil) appRoleName, err := client.GetAppRoleName() - assert.EqualError(t, err, "Token info did not contain metadata auth/token/lookup-self") + assert.EqualError(t, err, "token info did not contain metadata auth/token/lookup-self") assert.Empty(t, appRoleName) }) t.Run("Test without role name in metadata", func(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Read", "auth/token/lookup-self").Return(kv1Secret(SecretData{ "meta": SecretData{}, }), nil) @@ -280,7 +280,7 @@ func TestGetAppRoleName(t *testing.T) { t.Run("Test that different role_name types are ignored", func(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Read", "auth/token/lookup-self").Return(kv1Secret(SecretData{ "meta": SecretData{ "role_name": 5, @@ -297,7 +297,7 @@ func TestTokenRevocation(t *testing.T) { t.Parallel() t.Run("Test that revocation error is returned", func(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Write", "auth/token/revoke-self", mock.IsType(map[string]interface{}{})).Return(nil, errors.New("Test")) @@ -308,7 +308,7 @@ func TestTokenRevocation(t *testing.T) { t.Run("Test that revocation endpoint is called", func(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Write", "auth/token/revoke-self", mock.IsType(map[string]interface{}{})).Return(nil, nil) @@ -319,7 +319,7 @@ func TestTokenRevocation(t *testing.T) { func TestUnknownKvVersion(t *testing.T) { vaultMock := &mocks.VaultMock{} - client := Client{vaultMock, &Config{}} + client := Client{nil, vaultMock, &ClientConfig{}} vaultMock.On("Read", "sys/internal/ui/mounts/secret/secret").Return(&api.Secret{ Data: map[string]interface{}{ @@ -335,15 +335,6 @@ func TestUnknownKvVersion(t *testing.T) { } -func TestSetAppRoleMountPont(t *testing.T) { - client := Client{nil, &Config{}} - const newMountpoint = "auth/test" - - client.SetAppRoleMountPoint("auth/test") - - assert.Equal(t, newMountpoint, client.config.AppRoleMountPoint) -} - func setupMockKvV2(vaultMock *mocks.VaultMock) { vaultMock.On("Read", mock.MatchedBy(func(path string) bool { return strings.HasPrefix(path, sysLookupPath)