@@ -7,10 +7,13 @@ import (
77 "github.com/Azure/go-autorest/autorest"
88 "github.com/Azure/go-autorest/autorest/azure"
99 "github.com/Azure/go-autorest/autorest/azure/auth"
10+ "github.com/Azure/go-autorest/autorest/date"
1011 log "github.com/sirupsen/logrus"
1112 "github.com/webdevops/kube-bootstrap-token-manager/bootstraptoken"
1213 "github.com/webdevops/kube-bootstrap-token-manager/config"
1314 "os"
15+ "regexp"
16+ "time"
1417)
1518
1619type (
@@ -75,32 +78,13 @@ func (m *CloudProviderAzure) Init(ctx context.Context, opts config.Opts) {
7578func (m * CloudProviderAzure ) FetchToken () (token * bootstraptoken.BootstrapToken ) {
7679 vaultName := * m .opts .CloudProvider .Azure .KeyVaultName
7780 secretName := * m .opts .CloudProvider .Azure .KeyVaultSecretName
78- vaultUrl := fmt .Sprintf (
79- "https://%s.%s" ,
80- vaultName ,
81- m .environment .KeyVaultDNSSuffix ,
82- )
8381
84- log .Infof ("fetching newest token from Azure KeyVault \" %s\" secret \" %s\" " , vaultName , secretName )
85- secret , err := m .keyvaultClient .GetSecret (m .ctx , vaultUrl , secretName , "" )
86- if err != nil {
87- switch m .getInnerErrorCodeFromAutorestError (err ) {
88- case "SecretNotFound" :
89- // no secret found, need to create new token
90- log .Warn ("no secret found, assuming non existing token" )
91- break
92- case "SecretDisabled" :
93- // disabled secret, continue as there would be no token
94- log .Warn ("current secret is disabled, assuming non existing token" )
95- break
96- case "ForbiddenByPolicy" :
97- // access is forbidden
98- log .Error ("unable to access Azure KeyVault, please check access" )
99- log .Panic (err )
100- default :
101- // not handled error
102- log .Panic (err )
103- }
82+ contextLogger := log .WithFields (log.Fields {"keyVault" : vaultName , "secretName" : secretName })
83+
84+ contextLogger .Infof ("fetching newest token from Azure KeyVault \" %s\" secret \" %s\" " , vaultName , secretName )
85+ secret , err := m .keyvaultClient .GetSecret (m .ctx , m .getKeyVaultUrl (), secretName , "" )
86+ if m .handleKeyvaultError (err , contextLogger ) != nil {
87+ contextLogger .Panic (err )
10488 }
10589
10690 if secret .Value != nil {
@@ -119,15 +103,71 @@ func (m *CloudProviderAzure) FetchToken() (token *bootstraptoken.BootstrapToken)
119103 return
120104}
121105
106+ func (m * CloudProviderAzure ) FetchTokens () (tokens []* bootstraptoken.BootstrapToken ) {
107+ tokens = []* bootstraptoken.BootstrapToken {}
108+ vaultName := * m .opts .CloudProvider .Azure .KeyVaultName
109+ secretName := * m .opts .CloudProvider .Azure .KeyVaultSecretName
110+
111+ contextLogger := log .WithFields (log.Fields {"keyVault" : vaultName , "secretName" : secretName })
112+
113+ maxResults := int32 (15 )
114+
115+ contextLogger .Infof ("fetching all tokens from Azure KeyVault \" %s\" secret \" %s\" " , vaultName , secretName )
116+ list , err := m .keyvaultClient .GetSecretVersions (m .ctx , m .getKeyVaultUrl (), secretName , & maxResults )
117+ if m .handleKeyvaultError (err , contextLogger ) != nil {
118+ contextLogger .Panic (err )
119+ }
120+
121+ for _ , secret := range list .Values () {
122+ secretVersion := m .getSecretVersionFromId (* secret .ID )
123+ secretLogger := contextLogger .WithField ("secretVersion" , secretVersion )
124+
125+ // ignore not enabled
126+ if secret .Attributes .Enabled != nil && * secret .Attributes .Enabled == false {
127+ secretLogger .Debug ("ignoring, secret is disabled" )
128+ continue
129+ }
130+
131+ // ignore expired secrets
132+ if secret .Attributes .Expires != nil {
133+ expirationTime := date .UnixEpoch ().Add (secret .Attributes .Expires .Duration ())
134+ if expirationTime .Before (time .Now ()) {
135+ secretLogger .Debug ("ignoring, secret is expired" )
136+ continue
137+ }
138+ }
139+
140+ if secretVersion != "" {
141+ secret , err := m .keyvaultClient .GetSecret (m .ctx , m .getKeyVaultUrl (), secretName , secretVersion )
142+ if m .handleKeyvaultError (err , contextLogger ) != nil {
143+ secretLogger .Panic (err )
144+ }
145+
146+ if secret .Value != nil {
147+ token := bootstraptoken .ParseFromString (* secret .Value )
148+ if token != nil {
149+ secretLogger .Info ("found valid secret" )
150+ if secret .Attributes .Created != nil {
151+ token .SetCreationUnixTime (* secret .Attributes .Created )
152+ }
153+
154+ if secret .Attributes .Expires != nil {
155+ token .SetExpirationUnixTime (* secret .Attributes .Expires )
156+ }
157+
158+ tokens = append (tokens , token )
159+ }
160+ }
161+ }
162+ }
163+
164+ return
165+ }
166+
122167func (m * CloudProviderAzure ) StoreToken (token * bootstraptoken.BootstrapToken ) {
123168 contextLogger := m .log .WithFields (log.Fields {"token" : token .Id ()})
124169 vaultName := * m .opts .CloudProvider .Azure .KeyVaultName
125170 secretName := * m .opts .CloudProvider .Azure .KeyVaultSecretName
126- vaultUrl := fmt .Sprintf (
127- "https://%s.%s" ,
128- vaultName ,
129- m .environment .KeyVaultDNSSuffix ,
130- )
131171
132172 contextLogger .Infof ("storing token to Azure KeyVault \" %s\" secret \" %s\" with expiration %s" , vaultName , secretName , token .ExpirationString ())
133173
@@ -143,12 +183,59 @@ func (m *CloudProviderAzure) StoreToken(token *bootstraptoken.BootstrapToken) {
143183 Expires : token .ExpirationUnixTime (),
144184 },
145185 }
146- _ , err := m .keyvaultClient .SetSecret (m .ctx , vaultUrl , secretName , secretParameters )
186+ _ , err := m .keyvaultClient .SetSecret (m .ctx , m . getKeyVaultUrl () , secretName , secretParameters )
147187 if err != nil {
148188 log .Panic (err )
149189 }
150190}
151191
192+ func (m * CloudProviderAzure ) getKeyVaultUrl () (vaultUrl string ) {
193+ vaultName := * m .opts .CloudProvider .Azure .KeyVaultName
194+ vaultUrl = fmt .Sprintf (
195+ "https://%s.%s" ,
196+ vaultName ,
197+ m .environment .KeyVaultDNSSuffix ,
198+ )
199+
200+ return
201+ }
202+
203+ func (m * CloudProviderAzure ) getSecretVersionFromId (secretId string ) (version string ) {
204+ const resourceIDPatternText = `https://(.+)/secrets/(.+)/(.+)`
205+ resourceIDPattern := regexp .MustCompile (resourceIDPatternText )
206+ match := resourceIDPattern .FindStringSubmatch (secretId )
207+
208+ if len (match ) == 4 {
209+ return match [3 ]
210+ }
211+
212+ return ""
213+ }
214+
215+ func (m * CloudProviderAzure ) handleKeyvaultError (err error , logger * log.Entry ) (error ) {
216+ if err != nil {
217+ switch m .getInnerErrorCodeFromAutorestError (err ) {
218+ case "SecretNotFound" :
219+ // no secret found, need to create new token
220+ logger .Warn ("no secret found, assuming non existing token" )
221+ break
222+ case "SecretDisabled" :
223+ // disabled secret, continue as there would be no token
224+ logger .Warn ("current secret is disabled, assuming non existing token" )
225+ break
226+ case "ForbiddenByPolicy" :
227+ // access is forbidden
228+ logger .Error ("unable to access Azure KeyVault, please check access" )
229+ return err
230+ default :
231+ // not handled error
232+ return err
233+ }
234+ }
235+ return nil
236+ }
237+
238+
152239func (m * CloudProviderAzure ) getInnerErrorCodeFromAutorestError (err error ) (code interface {}) {
153240 if autorestError , ok := err .(autorest.DetailedError ); ok {
154241 if azureRequestError , ok := autorestError .Original .(* azure.RequestError ); ok {
0 commit comments