@@ -4,9 +4,12 @@ import (
44 "errors"
55 "fmt"
66 "os"
7+ "sync"
78
89 rabbitmqv1beta1 "github.com/rabbitmq/cluster-operator/api/v1beta1"
910
11+ ctrl "sigs.k8s.io/controller-runtime"
12+
1013 vault "github.com/hashicorp/vault/api"
1114)
1215
@@ -38,9 +41,42 @@ type VaultClient struct {
3841 Reader SecretReader
3942}
4043
41- var ServiceAccountTokenReader = ReadServiceAccountToken
42- var VaultClientTokenReader = ReadVaultClientToken
43- var VaultAuthenticator = LoginToVault
44+ var ReadServiceAccountTokenFunc = ReadServiceAccountToken
45+ var ReadVaultClientSecretFunc = ReadVaultClientSecret
46+ var LoginToVaultFunc = LoginToVault
47+
48+ var createSecretStoreClientOnce sync.Once
49+ var SecretClient SecretStoreClient
50+ var SecretClientCreationError error
51+
52+ func GetSecretStoreClient (vaultSpec * rabbitmqv1beta1.VaultSpec ) (SecretStoreClient , error ) {
53+ createSecretStoreClientOnce .Do (InitializeClient (vaultSpec ))
54+ return SecretClient , SecretClientCreationError
55+ }
56+
57+ func InitializeClient (vaultSpec * rabbitmqv1beta1.VaultSpec ) func () {
58+ return func () {
59+ // VAULT_ADDR environment variable will be the address that pod uses to communicate with Vault.
60+ config := vault .DefaultConfig () // modify for more granular configuration
61+ vaultClient , err := vault .NewClient (config )
62+ if err != nil {
63+ SecretClientCreationError = fmt .Errorf ("unable to initialize Vault client: %w" , err )
64+ return
65+ }
66+
67+ _ , err = login (vaultClient , vaultSpec )
68+ if err != nil {
69+ SecretClientCreationError = fmt .Errorf ("unable to login to Vault: %w" , err )
70+ return
71+ }
72+
73+ SecretClient = VaultClient {
74+ Reader : & VaultSecretReader {client : vaultClient },
75+ }
76+
77+ go renewToken (vaultClient , vaultSpec )
78+ }
79+ }
4480
4581func (vc VaultClient ) ReadCredentials (path string ) (CredentialsProvider , error ) {
4682 secret , err := vc .Reader .ReadSecret (path )
@@ -110,46 +146,105 @@ func availableKeys(m map[string]interface{}) []string {
110146 return result
111147}
112148
113- func InitializeSecretStoreClient (vaultSpec * rabbitmqv1beta1.VaultSpec ) (SecretStoreClient , error ) {
149+ func login (vaultClient * vault.Client , vaultSpec * rabbitmqv1beta1.VaultSpec ) (* vault.Secret , error ) {
150+ logger := ctrl .LoggerFrom (nil )
151+
114152 // GCH TODO return to this...
115153 // role := vaultSpec.Role
116154 // if role == "" {
117155 // return nil, errors.New("no role value set in Vault secret backend")
118156 // }
119157 role := "messaging-topology-operator"
120158
121- // For now, the VAULT_ADDR environment variable will be the address that your pod uses to communicate with Vault.
122- config := vault .DefaultConfig () // modify for more granular configuration
123-
124- vaultClient , err := vault .NewClient (config )
125- if err != nil {
126- return nil , fmt .Errorf ("unable to initialize Vault client: %w" , err )
127- }
128-
129159 var annotations = vaultSpec .Annotations
130160 if annotations ["vault.hashicorp.com/namespace" ] != "" {
131161 vaultClient .SetNamespace (annotations ["vault.hashicorp.com/namespace" ])
132162 }
133163
134- jwt , err := ServiceAccountTokenReader ()
164+ jwt , err := ReadServiceAccountTokenFunc ()
135165 if err != nil {
136166 return nil , fmt .Errorf ("unable to read file containing service account token: %w" , err )
137167 }
138168
139169 loginAuthPath := defaultAuthPath
170+ annotations = vaultSpec .Annotations
140171 if annotations ["vault.hashicorp.com/auth-path" ] != "" {
141172 loginAuthPath = annotations ["vault.hashicorp.com/auth-path" ]
142173 }
143174
144- vaultToken , err := VaultClientTokenReader (vaultClient , string (jwt ), role , loginAuthPath )
175+ logger .Info ("Authenticating to Vault" )
176+
177+ vaultSecret , err := ReadVaultClientSecretFunc (vaultClient , string (jwt ), role , loginAuthPath )
145178 if err != nil {
146- return nil , fmt .Errorf ("unable to read Vault client token: %w" , err )
179+ return nil , fmt .Errorf ("unable to obtain Vault client secret: %w" , err )
180+ }
181+
182+ if vaultSecret == nil || vaultSecret .Auth == nil || vaultSecret .Auth .ClientToken == "" {
183+ return nil , fmt .Errorf ("no client token found in Vault secret" )
184+ }
185+
186+ vaultClient .SetToken (vaultSecret .Auth .ClientToken )
187+ return vaultSecret , nil
188+ }
189+
190+ func renewToken (client * vault.Client , vaultSpec * rabbitmqv1beta1.VaultSpec ) {
191+ logger := ctrl .LoggerFrom (nil )
192+
193+ for {
194+ vaultLoginResp , err := login (client , vaultSpec )
195+ if err != nil {
196+ logger .Error (err , "unable to authenticate to Vault server" )
197+ }
198+
199+ err = manageTokenLifecycle (client , vaultLoginResp )
200+ if err != nil {
201+ logger .Error (err , "unable to start managing the Vault token lifecycle" )
202+ }
147203 }
204+ }
148205
149- // use the Vault token for making all future calls to Vault
150- vaultClient .SetToken (vaultToken )
206+ func manageTokenLifecycle (client * vault.Client , token * vault.Secret ) error {
207+ logger := ctrl .LoggerFrom (nil )
208+
209+ if token == nil || token .Auth == nil {
210+ logger .Info ("No Vault secret available. Re-attempting login" )
211+ return nil
212+ }
213+
214+ renew := token .Auth .Renewable
215+ if ! renew {
216+ logger .Info ("Token is not configured to be renewable. Re-attempting login" )
217+ return nil
218+ }
151219
152- return VaultClient {Reader : & VaultSecretReader {client : vaultClient }}, nil
220+ watcher , err := client .NewLifetimeWatcher (& vault.LifetimeWatcherInput {
221+ Secret : token ,
222+ })
223+ if err != nil {
224+ return fmt .Errorf ("unable to initialize new lifetime watcher for renewing auth token: %w" , err )
225+ }
226+
227+ go watcher .Start ()
228+ defer watcher .Stop ()
229+
230+ for {
231+ select {
232+ // `DoneCh` will return if renewal fails, or if the remaining lease duration is
233+ // under a built-in threshold and either renewing is not extending it or
234+ // renewing is disabled. In any case, the caller needs to attempt to log in again.
235+ case err := <- watcher .DoneCh ():
236+ if err != nil {
237+ logger .Error (err , "Failed to renew Vault token. Re-attempting login" )
238+ return nil
239+ }
240+ logger .Info ("Token can no longer be renewed. Re-attempting login." )
241+ return nil
242+
243+ // Successfully completed renewal
244+ case renewal := <- watcher .RenewCh ():
245+ logger .Info ("Successfully renewed Vault token" , "renewal info" , renewal )
246+ }
247+ }
153248}
154249
155250func ReadServiceAccountToken () ([]byte , error ) {
@@ -164,23 +259,13 @@ func ReadServiceAccountToken() ([]byte, error) {
164259 return token , nil
165260}
166261
167- func ReadVaultClientToken (vaultClient * vault.Client , jwtToken string , vaultRole string , authPath string ) (string , error ) {
262+ func ReadVaultClientSecret (vaultClient * vault.Client , jwtToken string , vaultRole string , authPath string ) (* vault. Secret , error ) {
168263 params := map [string ]interface {}{
169264 "jwt" : jwtToken ,
170265 "role" : vaultRole , // the name of the role in Vault that was created with this app's Kubernetes service account bound to it
171266 }
172267
173- // log in to Vault's Kubernetes auth method
174- resp , err := VaultAuthenticator (vaultClient , authPath , params )
175- if err != nil {
176- return "" , fmt .Errorf ("unable to log in with Kubernetes auth: %w" , err )
177- }
178-
179- // return the Vault client token provided in the login response
180- if resp == nil || resp .Auth == nil || resp .Auth .ClientToken == "" {
181- return "" , fmt .Errorf ("no client token found in Vault login response" )
182- }
183- return resp .Auth .ClientToken , nil
268+ return LoginToVaultFunc (vaultClient , authPath , params )
184269}
185270
186271func LoginToVault (vaultClient * vault.Client , authPath string , params map [string ]interface {}) (* vault.Secret , error ) {
0 commit comments