Skip to content

Commit c2c4019

Browse files
committed
Implement full sync (full token history)
Signed-off-by: Markus Blaschke <mblaschke82@gmail.com>
1 parent 4613a19 commit c2c4019

File tree

4 files changed

+142
-31
lines changed

4 files changed

+142
-31
lines changed

cloudprovider/azure.go

Lines changed: 118 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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

1619
type (
@@ -75,32 +78,13 @@ func (m *CloudProviderAzure) Init(ctx context.Context, opts config.Opts) {
7578
func (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+
122167
func (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+
152239
func (m *CloudProviderAzure) getInnerErrorCodeFromAutorestError(err error) (code interface{}) {
153240
if autorestError, ok := err.(autorest.DetailedError); ok {
154241
if azureRequestError, ok := autorestError.Original.(*azure.RequestError); ok {

cloudprovider/base.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type (
1111
CloudProvider interface {
1212
Init(ctx context.Context, opts config.Opts)
1313
FetchToken() (token *bootstraptoken.BootstrapToken)
14+
FetchTokens() (token []*bootstraptoken.BootstrapToken)
1415
StoreToken(token *bootstraptoken.BootstrapToken)
1516
}
1617
)

config/opts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type (
3232
Sync struct {
3333
Time time.Duration `long:"sync.time" env:"SYNC_TIME" description:"Sync time (time.Duration)" default:"1h"`
3434
RecreateBefore time.Duration `long:"sync.recreate-before" env:"SYNC_RECREATE_BEFORE" description:"Time duration (time.Duration) when token should be recreated" default:"2190h"`
35+
Full bool `long:"sync.full" env:"SYNC_FULL" description:"Sync also previous tokens (full sync)"`
3536
}
3637

3738
CloudProvider struct {

manager/manager.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ func (m *KubeBootstrapTokenManager) initCloudProvider() {
140140

141141
func (m *KubeBootstrapTokenManager) Start() {
142142
go func() {
143+
if m.Opts.Sync.Full {
144+
log.Infof("starting full sync run")
145+
if err := m.syncRunFull(); err != nil {
146+
log.Error(err)
147+
}
148+
}
143149
for {
144150
log.Infof("starting sync run")
145151
if err := m.syncRun(); err == nil {
@@ -155,6 +161,22 @@ func (m *KubeBootstrapTokenManager) Start() {
155161
}()
156162
}
157163

164+
func (m *KubeBootstrapTokenManager) syncRunFull() error {
165+
for _, token := range m.cloudProvider.FetchTokens() {
166+
contextLogger := log.WithFields(log.Fields{"token": token.Id()})
167+
contextLogger.Infof("found cloud token with id \"%s\" and expiration %s", token.Id(), token.ExpirationString())
168+
if !m.checkTokenRenewal(token) {
169+
contextLogger.Infof("valid cloud token, syncing to cluster")
170+
// sync token
171+
if err := m.createOrUpdateToken(token, false); err != nil {
172+
return err
173+
}
174+
}
175+
}
176+
177+
return nil
178+
}
179+
158180
func (m *KubeBootstrapTokenManager) syncRun() error {
159181
if token := m.cloudProvider.FetchToken(); token != nil {
160182
contextLogger := log.WithFields(log.Fields{"token": token.Id()})

0 commit comments

Comments
 (0)