Skip to content
This repository was archived by the owner on Feb 8, 2021. It is now read-only.

Commit 38a1042

Browse files
committed
Add a 5x exponential backoff on 429s & 5xxs to the webhook Authenticator/Authorizer.
1 parent e294b23 commit 38a1042

File tree

5 files changed

+59
-10
lines changed

5 files changed

+59
-10
lines changed

plugin/pkg/auth/authenticator/token/webhook/webhook.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"k8s.io/kubernetes/pkg/apis/authentication.k8s.io/v1beta1"
2626
"k8s.io/kubernetes/pkg/auth/authenticator"
2727
"k8s.io/kubernetes/pkg/auth/user"
28+
"k8s.io/kubernetes/pkg/client/restclient"
2829
"k8s.io/kubernetes/pkg/util/cache"
2930
"k8s.io/kubernetes/plugin/pkg/webhook"
3031

@@ -35,6 +36,8 @@ var (
3536
groupVersions = []unversioned.GroupVersion{v1beta1.SchemeGroupVersion}
3637
)
3738

39+
const retryBackoff = 500 * time.Millisecond
40+
3841
// Ensure WebhookTokenAuthenticator implements the authenticator.Token interface.
3942
var _ authenticator.Token = (*WebhookTokenAuthenticator)(nil)
4043

@@ -46,7 +49,12 @@ type WebhookTokenAuthenticator struct {
4649

4750
// New creates a new WebhookTokenAuthenticator from the provided kubeconfig file.
4851
func New(kubeConfigFile string, ttl time.Duration) (*WebhookTokenAuthenticator, error) {
49-
gw, err := webhook.NewGenericWebhook(kubeConfigFile, groupVersions)
52+
return newWithBackoff(kubeConfigFile, ttl, retryBackoff)
53+
}
54+
55+
// newWithBackoff allows tests to skip the sleep.
56+
func newWithBackoff(kubeConfigFile string, ttl, initialBackoff time.Duration) (*WebhookTokenAuthenticator, error) {
57+
gw, err := webhook.NewGenericWebhook(kubeConfigFile, groupVersions, initialBackoff)
5058
if err != nil {
5159
return nil, err
5260
}
@@ -61,7 +69,9 @@ func (w *WebhookTokenAuthenticator) AuthenticateToken(token string) (user.Info,
6169
if entry, ok := w.responseCache.Get(r.Spec); ok {
6270
r.Status = entry.(v1beta1.TokenReviewStatus)
6371
} else {
64-
result := w.RestClient.Post().Body(r).Do()
72+
result := w.WithExponentialBackoff(func() restclient.Result {
73+
return w.RestClient.Post().Body(r).Do()
74+
})
6575
if err := result.Error(); err != nil {
6676
return nil, false, err
6777
}

plugin/pkg/auth/authenticator/token/webhook/webhook_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ func newTokenAuthenticator(serverURL string, clientCert, clientKey, ca []byte, c
148148
if err := json.NewEncoder(tempfile).Encode(config); err != nil {
149149
return nil, err
150150
}
151-
return New(p, cacheTime)
151+
return newWithBackoff(p, cacheTime, 0)
152152
}
153153

154154
func TestTLSConfig(t *testing.T) {

plugin/pkg/auth/authorizer/webhook/webhook.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"k8s.io/kubernetes/pkg/api/unversioned"
2727
"k8s.io/kubernetes/pkg/apis/authorization/v1beta1"
2828
"k8s.io/kubernetes/pkg/auth/authorizer"
29+
"k8s.io/kubernetes/pkg/client/restclient"
2930
"k8s.io/kubernetes/pkg/util/cache"
3031
"k8s.io/kubernetes/plugin/pkg/webhook"
3132

@@ -36,6 +37,8 @@ var (
3637
groupVersions = []unversioned.GroupVersion{v1beta1.SchemeGroupVersion}
3738
)
3839

40+
const retryBackoff = 500 * time.Millisecond
41+
3942
// Ensure Webhook implements the authorizer.Authorizer interface.
4043
var _ authorizer.Authorizer = (*WebhookAuthorizer)(nil)
4144

@@ -67,7 +70,12 @@ type WebhookAuthorizer struct {
6770
// For additional HTTP configuration, refer to the kubeconfig documentation
6871
// http://kubernetes.io/v1.1/docs/user-guide/kubeconfig-file.html.
6972
func New(kubeConfigFile string, authorizedTTL, unauthorizedTTL time.Duration) (*WebhookAuthorizer, error) {
70-
gw, err := webhook.NewGenericWebhook(kubeConfigFile, groupVersions)
73+
return newWithBackoff(kubeConfigFile, authorizedTTL, unauthorizedTTL, retryBackoff)
74+
}
75+
76+
// newWithBackoff allows tests to skip the sleep.
77+
func newWithBackoff(kubeConfigFile string, authorizedTTL, unauthorizedTTL, initialBackoff time.Duration) (*WebhookAuthorizer, error) {
78+
gw, err := webhook.NewGenericWebhook(kubeConfigFile, groupVersions, initialBackoff)
7179
if err != nil {
7280
return nil, err
7381
}
@@ -148,7 +156,9 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (err error) {
148156
if entry, ok := w.responseCache.Get(string(key)); ok {
149157
r.Status = entry.(v1beta1.SubjectAccessReviewStatus)
150158
} else {
151-
result := w.RestClient.Post().Body(r).Do()
159+
result := w.WithExponentialBackoff(func() restclient.Result {
160+
return w.RestClient.Post().Body(r).Do()
161+
})
152162
if err := result.Error(); err != nil {
153163
return err
154164
}

plugin/pkg/auth/authorizer/webhook/webhook_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ current-context: default
183183
return fmt.Errorf("failed to execute test template: %v", err)
184184
}
185185
// Create a new authorizer
186-
_, err = New(p, 0, 0)
186+
_, err = newWithBackoff(p, 0, 0, 0)
187187
return err
188188
}()
189189
if err != nil && !tt.wantErr {
@@ -291,7 +291,7 @@ func newAuthorizer(callbackURL string, clientCert, clientKey, ca []byte, cacheTi
291291
if err := json.NewEncoder(tempfile).Encode(config); err != nil {
292292
return nil, err
293293
}
294-
return New(p, cacheTime, cacheTime)
294+
return newWithBackoff(p, cacheTime, cacheTime, 0)
295295
}
296296

297297
func TestTLSConfig(t *testing.T) {

plugin/pkg/webhook/webhook.go

+32-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package webhook
1919

2020
import (
2121
"fmt"
22+
"time"
2223

2324
"k8s.io/kubernetes/pkg/api"
2425
"k8s.io/kubernetes/pkg/api/unversioned"
@@ -27,16 +28,18 @@ import (
2728
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
2829
"k8s.io/kubernetes/pkg/runtime"
2930
runtimeserializer "k8s.io/kubernetes/pkg/runtime/serializer"
31+
"k8s.io/kubernetes/pkg/util/wait"
3032

3133
_ "k8s.io/kubernetes/pkg/apis/authorization/install"
3234
)
3335

3436
type GenericWebhook struct {
35-
RestClient *restclient.RESTClient
37+
RestClient *restclient.RESTClient
38+
initialBackoff time.Duration
3639
}
3740

3841
// New creates a new GenericWebhook from the provided kubeconfig file.
39-
func NewGenericWebhook(kubeConfigFile string, groupVersions []unversioned.GroupVersion) (*GenericWebhook, error) {
42+
func NewGenericWebhook(kubeConfigFile string, groupVersions []unversioned.GroupVersion, initialBackoff time.Duration) (*GenericWebhook, error) {
4043
for _, groupVersion := range groupVersions {
4144
if !registered.IsEnabledVersion(groupVersion) {
4245
return nil, fmt.Errorf("webhook plugin requires enabling extension resource: %s", groupVersion)
@@ -64,5 +67,31 @@ func NewGenericWebhook(kubeConfigFile string, groupVersions []unversioned.GroupV
6467

6568
// TODO(ericchiang): Can we ensure remote service is reachable?
6669

67-
return &GenericWebhook{restClient}, nil
70+
return &GenericWebhook{restClient, initialBackoff}, nil
71+
}
72+
73+
// WithExponentialBackoff will retry webhookFn 5 times w/ exponentially
74+
// increasing backoff when a 429 or a 5xx response code is returned.
75+
func (g *GenericWebhook) WithExponentialBackoff(webhookFn func() restclient.Result) restclient.Result {
76+
backoff := wait.Backoff{
77+
Duration: g.initialBackoff,
78+
Factor: 1.5,
79+
Jitter: 0.2,
80+
Steps: 5,
81+
}
82+
var result restclient.Result
83+
wait.ExponentialBackoff(backoff, func() (bool, error) {
84+
result = webhookFn()
85+
// Return from Request.Do() errors immediately.
86+
if err := result.Error(); err != nil {
87+
return false, err
88+
}
89+
// Retry 429s, and 5xxs.
90+
var statusCode int
91+
if result.StatusCode(&statusCode); statusCode == 429 || statusCode >= 500 {
92+
return false, nil
93+
}
94+
return true, nil
95+
})
96+
return result
6897
}

0 commit comments

Comments
 (0)