mirror of
https://github.com/niusmallnan/steve.git
synced 2025-09-25 04:11:57 +00:00
Fix update/patch and add webhook auth
This commit is contained in:
90
vendor/k8s.io/apiserver/pkg/authentication/authenticator/audagnostic.go
generated
vendored
Normal file
90
vendor/k8s.io/apiserver/pkg/authentication/authenticator/audagnostic.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package authenticator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func authenticate(ctx context.Context, implicitAuds Audiences, authenticate func() (*Response, bool, error)) (*Response, bool, error) {
|
||||
targetAuds, ok := AudiencesFrom(ctx)
|
||||
// We can remove this once api audiences is never empty. That will probably
|
||||
// be N releases after TokenRequest is GA.
|
||||
if !ok {
|
||||
return authenticate()
|
||||
}
|
||||
auds := implicitAuds.Intersect(targetAuds)
|
||||
if len(auds) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
resp, ok, err := authenticate()
|
||||
if err != nil || !ok {
|
||||
return nil, false, err
|
||||
}
|
||||
if len(resp.Audiences) > 0 {
|
||||
// maybe the authenticator was audience aware after all.
|
||||
return nil, false, fmt.Errorf("audience agnostic authenticator wrapped an authenticator that returned audiences: %q", resp.Audiences)
|
||||
}
|
||||
resp.Audiences = auds
|
||||
return resp, true, nil
|
||||
}
|
||||
|
||||
type audAgnosticRequestAuthenticator struct {
|
||||
implicit Audiences
|
||||
delegate Request
|
||||
}
|
||||
|
||||
var _ = Request(&audAgnosticRequestAuthenticator{})
|
||||
|
||||
func (a *audAgnosticRequestAuthenticator) AuthenticateRequest(req *http.Request) (*Response, bool, error) {
|
||||
return authenticate(req.Context(), a.implicit, func() (*Response, bool, error) {
|
||||
return a.delegate.AuthenticateRequest(req)
|
||||
})
|
||||
}
|
||||
|
||||
// WrapAudienceAgnosticRequest wraps an audience agnostic request authenticator
|
||||
// to restrict its accepted audiences to a set of implicit audiences.
|
||||
func WrapAudienceAgnosticRequest(implicit Audiences, delegate Request) Request {
|
||||
return &audAgnosticRequestAuthenticator{
|
||||
implicit: implicit,
|
||||
delegate: delegate,
|
||||
}
|
||||
}
|
||||
|
||||
type audAgnosticTokenAuthenticator struct {
|
||||
implicit Audiences
|
||||
delegate Token
|
||||
}
|
||||
|
||||
var _ = Token(&audAgnosticTokenAuthenticator{})
|
||||
|
||||
func (a *audAgnosticTokenAuthenticator) AuthenticateToken(ctx context.Context, tok string) (*Response, bool, error) {
|
||||
return authenticate(ctx, a.implicit, func() (*Response, bool, error) {
|
||||
return a.delegate.AuthenticateToken(ctx, tok)
|
||||
})
|
||||
}
|
||||
|
||||
// WrapAudienceAgnosticToken wraps an audience agnostic token authenticator to
|
||||
// restrict its accepted audiences to a set of implicit audiences.
|
||||
func WrapAudienceAgnosticToken(implicit Audiences, delegate Token) Token {
|
||||
return &audAgnosticTokenAuthenticator{
|
||||
implicit: implicit,
|
||||
delegate: delegate,
|
||||
}
|
||||
}
|
63
vendor/k8s.io/apiserver/pkg/authentication/authenticator/audiences.go
generated
vendored
Normal file
63
vendor/k8s.io/apiserver/pkg/authentication/authenticator/audiences.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package authenticator
|
||||
|
||||
import "context"
|
||||
|
||||
// Audiences is a container for the Audiences of a token.
|
||||
type Audiences []string
|
||||
|
||||
// The key type is unexported to prevent collisions
|
||||
type key int
|
||||
|
||||
const (
|
||||
// audiencesKey is the context key for request audiences.
|
||||
audiencesKey key = iota
|
||||
)
|
||||
|
||||
// WithAudiences returns a context that stores a request's expected audiences.
|
||||
func WithAudiences(ctx context.Context, auds Audiences) context.Context {
|
||||
return context.WithValue(ctx, audiencesKey, auds)
|
||||
}
|
||||
|
||||
// AudiencesFrom returns a request's expected audiences stored in the request context.
|
||||
func AudiencesFrom(ctx context.Context) (Audiences, bool) {
|
||||
auds, ok := ctx.Value(audiencesKey).(Audiences)
|
||||
return auds, ok
|
||||
}
|
||||
|
||||
// Has checks if Audiences contains a specific audiences.
|
||||
func (a Audiences) Has(taud string) bool {
|
||||
for _, aud := range a {
|
||||
if aud == taud {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Intersect intersects Audiences with a target Audiences and returns all
|
||||
// elements in both.
|
||||
func (a Audiences) Intersect(tauds Audiences) Audiences {
|
||||
selected := Audiences{}
|
||||
for _, taud := range tauds {
|
||||
if a.Has(taud) {
|
||||
selected = append(selected, taud)
|
||||
}
|
||||
}
|
||||
return selected
|
||||
}
|
80
vendor/k8s.io/apiserver/pkg/authentication/authenticator/interfaces.go
generated
vendored
Normal file
80
vendor/k8s.io/apiserver/pkg/authentication/authenticator/interfaces.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package authenticator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
// Token checks a string value against a backing authentication store and
|
||||
// returns a Response or an error if the token could not be checked.
|
||||
type Token interface {
|
||||
AuthenticateToken(ctx context.Context, token string) (*Response, bool, error)
|
||||
}
|
||||
|
||||
// Request attempts to extract authentication information from a request and
|
||||
// returns a Response or an error if the request could not be checked.
|
||||
type Request interface {
|
||||
AuthenticateRequest(req *http.Request) (*Response, bool, error)
|
||||
}
|
||||
|
||||
// Password checks a username and password against a backing authentication
|
||||
// store and returns a Response or an error if the password could not be
|
||||
// checked.
|
||||
type Password interface {
|
||||
AuthenticatePassword(ctx context.Context, user, password string) (*Response, bool, error)
|
||||
}
|
||||
|
||||
// TokenFunc is a function that implements the Token interface.
|
||||
type TokenFunc func(ctx context.Context, token string) (*Response, bool, error)
|
||||
|
||||
// AuthenticateToken implements authenticator.Token.
|
||||
func (f TokenFunc) AuthenticateToken(ctx context.Context, token string) (*Response, bool, error) {
|
||||
return f(ctx, token)
|
||||
}
|
||||
|
||||
// RequestFunc is a function that implements the Request interface.
|
||||
type RequestFunc func(req *http.Request) (*Response, bool, error)
|
||||
|
||||
// AuthenticateRequest implements authenticator.Request.
|
||||
func (f RequestFunc) AuthenticateRequest(req *http.Request) (*Response, bool, error) {
|
||||
return f(req)
|
||||
}
|
||||
|
||||
// PasswordFunc is a function that implements the Password interface.
|
||||
type PasswordFunc func(ctx context.Context, user, password string) (*Response, bool, error)
|
||||
|
||||
// AuthenticatePassword implements authenticator.Password.
|
||||
func (f PasswordFunc) AuthenticatePassword(ctx context.Context, user, password string) (*Response, bool, error) {
|
||||
return f(ctx, user, password)
|
||||
}
|
||||
|
||||
// Response is the struct returned by authenticator interfaces upon successful
|
||||
// authentication. It contains information about whether the authenticator
|
||||
// authenticated the request, information about the context of the
|
||||
// authentication, and information about the authenticated user.
|
||||
type Response struct {
|
||||
// Audiences is the set of audiences the authenticator was able to validate
|
||||
// the token against. If the authenticator is not audience aware, this field
|
||||
// will be empty.
|
||||
Audiences Audiences
|
||||
// User is the UserInfo associated with the authentication context.
|
||||
User user.Info
|
||||
}
|
211
vendor/k8s.io/apiserver/pkg/util/webhook/authentication.go
generated
vendored
Normal file
211
vendor/k8s.io/apiserver/pkg/util/webhook/authentication.go
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
// AuthenticationInfoResolverWrapper can be used to inject Dial function to the
|
||||
// rest.Config generated by the resolver.
|
||||
type AuthenticationInfoResolverWrapper func(AuthenticationInfoResolver) AuthenticationInfoResolver
|
||||
|
||||
// NewDefaultAuthenticationInfoResolverWrapper builds a default authn resolver wrapper
|
||||
func NewDefaultAuthenticationInfoResolverWrapper(
|
||||
proxyTransport *http.Transport,
|
||||
kubeapiserverClientConfig *rest.Config) AuthenticationInfoResolverWrapper {
|
||||
|
||||
webhookAuthResolverWrapper := func(delegate AuthenticationInfoResolver) AuthenticationInfoResolver {
|
||||
return &AuthenticationInfoResolverDelegator{
|
||||
ClientConfigForFunc: func(server string) (*rest.Config, error) {
|
||||
if server == "kubernetes.default.svc" {
|
||||
return kubeapiserverClientConfig, nil
|
||||
}
|
||||
return delegate.ClientConfigFor(server)
|
||||
},
|
||||
ClientConfigForServiceFunc: func(serviceName, serviceNamespace string) (*rest.Config, error) {
|
||||
if serviceName == "kubernetes" && serviceNamespace == corev1.NamespaceDefault {
|
||||
return kubeapiserverClientConfig, nil
|
||||
}
|
||||
ret, err := delegate.ClientConfigForService(serviceName, serviceNamespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if proxyTransport != nil && proxyTransport.DialContext != nil {
|
||||
ret.Dial = proxyTransport.DialContext
|
||||
}
|
||||
return ret, err
|
||||
},
|
||||
}
|
||||
}
|
||||
return webhookAuthResolverWrapper
|
||||
}
|
||||
|
||||
// AuthenticationInfoResolver builds rest.Config base on the server or service
|
||||
// name and service namespace.
|
||||
type AuthenticationInfoResolver interface {
|
||||
// ClientConfigFor builds rest.Config based on the server.
|
||||
ClientConfigFor(server string) (*rest.Config, error)
|
||||
// ClientConfigForService builds rest.Config based on the serviceName and
|
||||
// serviceNamespace.
|
||||
ClientConfigForService(serviceName, serviceNamespace string) (*rest.Config, error)
|
||||
}
|
||||
|
||||
// AuthenticationInfoResolverDelegator implements AuthenticationInfoResolver.
|
||||
type AuthenticationInfoResolverDelegator struct {
|
||||
ClientConfigForFunc func(server string) (*rest.Config, error)
|
||||
ClientConfigForServiceFunc func(serviceName, serviceNamespace string) (*rest.Config, error)
|
||||
}
|
||||
|
||||
// ClientConfigFor returns client config for given server.
|
||||
func (a *AuthenticationInfoResolverDelegator) ClientConfigFor(server string) (*rest.Config, error) {
|
||||
return a.ClientConfigForFunc(server)
|
||||
}
|
||||
|
||||
// ClientConfigForService returns client config for given service.
|
||||
func (a *AuthenticationInfoResolverDelegator) ClientConfigForService(serviceName, serviceNamespace string) (*rest.Config, error) {
|
||||
return a.ClientConfigForServiceFunc(serviceName, serviceNamespace)
|
||||
}
|
||||
|
||||
type defaultAuthenticationInfoResolver struct {
|
||||
kubeconfig clientcmdapi.Config
|
||||
}
|
||||
|
||||
// NewDefaultAuthenticationInfoResolver generates an AuthenticationInfoResolver
|
||||
// that builds rest.Config based on the kubeconfig file. kubeconfigFile is the
|
||||
// path to the kubeconfig.
|
||||
func NewDefaultAuthenticationInfoResolver(kubeconfigFile string) (AuthenticationInfoResolver, error) {
|
||||
if len(kubeconfigFile) == 0 {
|
||||
return &defaultAuthenticationInfoResolver{}, nil
|
||||
}
|
||||
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
loadingRules.ExplicitPath = kubeconfigFile
|
||||
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
|
||||
clientConfig, err := loader.RawConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &defaultAuthenticationInfoResolver{kubeconfig: clientConfig}, nil
|
||||
}
|
||||
|
||||
func (c *defaultAuthenticationInfoResolver) ClientConfigFor(server string) (*rest.Config, error) {
|
||||
return c.clientConfig(server)
|
||||
}
|
||||
|
||||
func (c *defaultAuthenticationInfoResolver) ClientConfigForService(serviceName, serviceNamespace string) (*rest.Config, error) {
|
||||
return c.clientConfig(serviceName + "." + serviceNamespace + ".svc")
|
||||
}
|
||||
|
||||
func (c *defaultAuthenticationInfoResolver) clientConfig(target string) (*rest.Config, error) {
|
||||
// exact match
|
||||
if authConfig, ok := c.kubeconfig.AuthInfos[target]; ok {
|
||||
return restConfigFromKubeconfig(authConfig)
|
||||
}
|
||||
|
||||
// star prefixed match
|
||||
serverSteps := strings.Split(target, ".")
|
||||
for i := 1; i < len(serverSteps); i++ {
|
||||
nickName := "*." + strings.Join(serverSteps[i:], ".")
|
||||
if authConfig, ok := c.kubeconfig.AuthInfos[nickName]; ok {
|
||||
return restConfigFromKubeconfig(authConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// if we're trying to hit the kube-apiserver and there wasn't an explicit config, use the in-cluster config
|
||||
if target == "kubernetes.default.svc" {
|
||||
// if we can find an in-cluster-config use that. If we can't, fall through.
|
||||
inClusterConfig, err := rest.InClusterConfig()
|
||||
if err == nil {
|
||||
return setGlobalDefaults(inClusterConfig), nil
|
||||
}
|
||||
}
|
||||
|
||||
// star (default) match
|
||||
if authConfig, ok := c.kubeconfig.AuthInfos["*"]; ok {
|
||||
return restConfigFromKubeconfig(authConfig)
|
||||
}
|
||||
|
||||
// use the current context from the kubeconfig if possible
|
||||
if len(c.kubeconfig.CurrentContext) > 0 {
|
||||
if currContext, ok := c.kubeconfig.Contexts[c.kubeconfig.CurrentContext]; ok {
|
||||
if len(currContext.AuthInfo) > 0 {
|
||||
if currAuth, ok := c.kubeconfig.AuthInfos[currContext.AuthInfo]; ok {
|
||||
return restConfigFromKubeconfig(currAuth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// anonymous
|
||||
return setGlobalDefaults(&rest.Config{}), nil
|
||||
}
|
||||
|
||||
func restConfigFromKubeconfig(configAuthInfo *clientcmdapi.AuthInfo) (*rest.Config, error) {
|
||||
config := &rest.Config{}
|
||||
|
||||
// blindly overwrite existing values based on precedence
|
||||
if len(configAuthInfo.Token) > 0 {
|
||||
config.BearerToken = configAuthInfo.Token
|
||||
} else if len(configAuthInfo.TokenFile) > 0 {
|
||||
tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.BearerToken = string(tokenBytes)
|
||||
config.BearerTokenFile = configAuthInfo.TokenFile
|
||||
}
|
||||
if len(configAuthInfo.Impersonate) > 0 {
|
||||
config.Impersonate = rest.ImpersonationConfig{
|
||||
UserName: configAuthInfo.Impersonate,
|
||||
Groups: configAuthInfo.ImpersonateGroups,
|
||||
Extra: configAuthInfo.ImpersonateUserExtra,
|
||||
}
|
||||
}
|
||||
if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
|
||||
config.CertFile = configAuthInfo.ClientCertificate
|
||||
config.CertData = configAuthInfo.ClientCertificateData
|
||||
config.KeyFile = configAuthInfo.ClientKey
|
||||
config.KeyData = configAuthInfo.ClientKeyData
|
||||
}
|
||||
if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
|
||||
config.Username = configAuthInfo.Username
|
||||
config.Password = configAuthInfo.Password
|
||||
}
|
||||
if configAuthInfo.AuthProvider != nil {
|
||||
return nil, fmt.Errorf("auth provider not supported")
|
||||
}
|
||||
|
||||
return setGlobalDefaults(config), nil
|
||||
}
|
||||
|
||||
func setGlobalDefaults(config *rest.Config) *rest.Config {
|
||||
config.UserAgent = "kube-apiserver-admission"
|
||||
config.Timeout = 30 * time.Second
|
||||
|
||||
return config
|
||||
}
|
198
vendor/k8s.io/apiserver/pkg/util/webhook/client.go
generated
vendored
Normal file
198
vendor/k8s.io/apiserver/pkg/util/webhook/client.go
generated
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
"github.com/hashicorp/golang-lru"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCacheSize = 200
|
||||
)
|
||||
|
||||
// ClientConfig defines parameters required for creating a hook client.
|
||||
type ClientConfig struct {
|
||||
Name string
|
||||
URL string
|
||||
CABundle []byte
|
||||
Service *ClientConfigService
|
||||
}
|
||||
|
||||
// ClientConfigService defines service discovery parameters of the webhook.
|
||||
type ClientConfigService struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Path string
|
||||
}
|
||||
|
||||
// ClientManager builds REST clients to talk to webhooks. It caches the clients
|
||||
// to avoid duplicate creation.
|
||||
type ClientManager struct {
|
||||
authInfoResolver AuthenticationInfoResolver
|
||||
serviceResolver ServiceResolver
|
||||
negotiatedSerializer runtime.NegotiatedSerializer
|
||||
cache *lru.Cache
|
||||
}
|
||||
|
||||
// NewClientManager creates a clientManager.
|
||||
func NewClientManager(gv schema.GroupVersion, addToSchemaFunc func(s *runtime.Scheme) error) (ClientManager, error) {
|
||||
cache, err := lru.New(defaultCacheSize)
|
||||
if err != nil {
|
||||
return ClientManager{}, err
|
||||
}
|
||||
hookScheme := runtime.NewScheme()
|
||||
if err := addToSchemaFunc(hookScheme); err != nil {
|
||||
return ClientManager{}, err
|
||||
}
|
||||
return ClientManager{
|
||||
cache: cache,
|
||||
negotiatedSerializer: serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{
|
||||
Serializer: serializer.NewCodecFactory(hookScheme).LegacyCodec(gv),
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetAuthenticationInfoResolverWrapper sets the
|
||||
// AuthenticationInfoResolverWrapper.
|
||||
func (cm *ClientManager) SetAuthenticationInfoResolverWrapper(wrapper AuthenticationInfoResolverWrapper) {
|
||||
if wrapper != nil {
|
||||
cm.authInfoResolver = wrapper(cm.authInfoResolver)
|
||||
}
|
||||
}
|
||||
|
||||
// SetAuthenticationInfoResolver sets the AuthenticationInfoResolver.
|
||||
func (cm *ClientManager) SetAuthenticationInfoResolver(resolver AuthenticationInfoResolver) {
|
||||
cm.authInfoResolver = resolver
|
||||
}
|
||||
|
||||
// SetServiceResolver sets the ServiceResolver.
|
||||
func (cm *ClientManager) SetServiceResolver(sr ServiceResolver) {
|
||||
if sr != nil {
|
||||
cm.serviceResolver = sr
|
||||
}
|
||||
}
|
||||
|
||||
// Validate checks if ClientManager is properly set up.
|
||||
func (cm *ClientManager) Validate() error {
|
||||
var errs []error
|
||||
if cm.negotiatedSerializer == nil {
|
||||
errs = append(errs, fmt.Errorf("the clientManager requires a negotiatedSerializer"))
|
||||
}
|
||||
if cm.serviceResolver == nil {
|
||||
errs = append(errs, fmt.Errorf("the clientManager requires a serviceResolver"))
|
||||
}
|
||||
if cm.authInfoResolver == nil {
|
||||
errs = append(errs, fmt.Errorf("the clientManager requires an authInfoResolver"))
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// HookClient get a RESTClient from the cache, or constructs one based on the
|
||||
// webhook configuration.
|
||||
func (cm *ClientManager) HookClient(cc ClientConfig) (*rest.RESTClient, error) {
|
||||
ccWithNoName := cc
|
||||
ccWithNoName.Name = ""
|
||||
cacheKey, err := json.Marshal(ccWithNoName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if client, ok := cm.cache.Get(string(cacheKey)); ok {
|
||||
return client.(*rest.RESTClient), nil
|
||||
}
|
||||
|
||||
complete := func(cfg *rest.Config) (*rest.RESTClient, error) {
|
||||
// Combine CAData from the config with any existing CA bundle provided
|
||||
if len(cfg.TLSClientConfig.CAData) > 0 {
|
||||
cfg.TLSClientConfig.CAData = append(cfg.TLSClientConfig.CAData, '\n')
|
||||
}
|
||||
cfg.TLSClientConfig.CAData = append(cfg.TLSClientConfig.CAData, cc.CABundle...)
|
||||
|
||||
cfg.ContentConfig.NegotiatedSerializer = cm.negotiatedSerializer
|
||||
cfg.ContentConfig.ContentType = runtime.ContentTypeJSON
|
||||
client, err := rest.UnversionedRESTClientFor(cfg)
|
||||
if err == nil {
|
||||
cm.cache.Add(string(cacheKey), client)
|
||||
}
|
||||
return client, err
|
||||
}
|
||||
|
||||
if cc.Service != nil {
|
||||
restConfig, err := cm.authInfoResolver.ClientConfigForService(cc.Service.Name, cc.Service.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg := rest.CopyConfig(restConfig)
|
||||
serverName := cc.Service.Name + "." + cc.Service.Namespace + ".svc"
|
||||
host := serverName + ":443"
|
||||
cfg.Host = "https://" + host
|
||||
cfg.APIPath = cc.Service.Path
|
||||
// Set the server name if not already set
|
||||
if len(cfg.TLSClientConfig.ServerName) == 0 {
|
||||
cfg.TLSClientConfig.ServerName = serverName
|
||||
}
|
||||
|
||||
delegateDialer := cfg.Dial
|
||||
if delegateDialer == nil {
|
||||
var d net.Dialer
|
||||
delegateDialer = d.DialContext
|
||||
}
|
||||
cfg.Dial = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if addr == host {
|
||||
u, err := cm.serviceResolver.ResolveEndpoint(cc.Service.Namespace, cc.Service.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr = u.Host
|
||||
}
|
||||
return delegateDialer(ctx, network, addr)
|
||||
}
|
||||
|
||||
return complete(cfg)
|
||||
}
|
||||
|
||||
if cc.URL == "" {
|
||||
return nil, &ErrCallingWebhook{WebhookName: cc.Name, Reason: errors.New("webhook configuration must have either service or URL")}
|
||||
}
|
||||
|
||||
u, err := url.Parse(cc.URL)
|
||||
if err != nil {
|
||||
return nil, &ErrCallingWebhook{WebhookName: cc.Name, Reason: fmt.Errorf("Unparsable URL: %v", err)}
|
||||
}
|
||||
|
||||
restConfig, err := cm.authInfoResolver.ClientConfigFor(u.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := rest.CopyConfig(restConfig)
|
||||
cfg.Host = u.Scheme + "://" + u.Host
|
||||
cfg.APIPath = u.Path
|
||||
|
||||
return complete(cfg)
|
||||
}
|
34
vendor/k8s.io/apiserver/pkg/util/webhook/error.go
generated
vendored
Normal file
34
vendor/k8s.io/apiserver/pkg/util/webhook/error.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ErrCallingWebhook is returned for transport-layer errors calling webhooks. It
|
||||
// represents a failure to talk to the webhook, not the webhook rejecting a
|
||||
// request.
|
||||
type ErrCallingWebhook struct {
|
||||
WebhookName string
|
||||
Reason error
|
||||
}
|
||||
|
||||
func (e *ErrCallingWebhook) Error() string {
|
||||
if e.Reason != nil {
|
||||
return fmt.Sprintf("failed calling webhook %q: %v", e.WebhookName, e.Reason)
|
||||
}
|
||||
return fmt.Sprintf("failed calling webhook %q; no further details available", e.WebhookName)
|
||||
}
|
107
vendor/k8s.io/apiserver/pkg/util/webhook/gencerts.sh
generated
vendored
Normal file
107
vendor/k8s.io/apiserver/pkg/util/webhook/gencerts.sh
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2017 The Kubernetes Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
set -e
|
||||
|
||||
# gencerts.sh generates the certificates for the webhook tests.
|
||||
#
|
||||
# It is not expected to be run often (there is no go generate rule), and mainly
|
||||
# exists for documentation purposes.
|
||||
|
||||
CN_BASE="webhook_tests"
|
||||
|
||||
cat > server.conf << EOF
|
||||
[req]
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
[req_distinguished_name]
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = clientAuth, serverAuth
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
IP.1 = 127.0.0.1
|
||||
EOF
|
||||
|
||||
cat > client.conf << EOF
|
||||
[req]
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
[req_distinguished_name]
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = clientAuth, serverAuth
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
IP.1 = 127.0.0.1
|
||||
EOF
|
||||
|
||||
# Create a certificate authority
|
||||
openssl genrsa -out caKey.pem 2048
|
||||
openssl req -x509 -new -nodes -key caKey.pem -days 100000 -out caCert.pem -subj "/CN=${CN_BASE}_ca"
|
||||
|
||||
# Create a second certificate authority
|
||||
openssl genrsa -out badCAKey.pem 2048
|
||||
openssl req -x509 -new -nodes -key badCAKey.pem -days 100000 -out badCACert.pem -subj "/CN=${CN_BASE}_ca"
|
||||
|
||||
# Create a server certiticate
|
||||
openssl genrsa -out serverKey.pem 2048
|
||||
openssl req -new -key serverKey.pem -out server.csr -subj "/CN=${CN_BASE}_server" -config server.conf
|
||||
openssl x509 -req -in server.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out serverCert.pem -days 100000 -extensions v3_req -extfile server.conf
|
||||
|
||||
# Create a client certiticate
|
||||
openssl genrsa -out clientKey.pem 2048
|
||||
openssl req -new -key clientKey.pem -out client.csr -subj "/CN=${CN_BASE}_client" -config client.conf
|
||||
openssl x509 -req -in client.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out clientCert.pem -days 100000 -extensions v3_req -extfile client.conf
|
||||
|
||||
outfile=certs_test.go
|
||||
|
||||
cat > $outfile << EOF
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This file was generated using openssl by the gencerts.sh script
|
||||
// and holds raw certificates for the webhook tests.
|
||||
|
||||
package webhook
|
||||
EOF
|
||||
|
||||
for file in caKey caCert badCAKey badCACert serverKey serverCert clientKey clientCert; do
|
||||
data=$(cat ${file}.pem)
|
||||
echo "" >> $outfile
|
||||
echo "var $file = []byte(\`$data\`)" >> $outfile
|
||||
done
|
||||
|
||||
# Clean up after we're done.
|
||||
rm ./*.pem
|
||||
rm ./*.csr
|
||||
rm ./*.srl
|
||||
rm ./*.conf
|
46
vendor/k8s.io/apiserver/pkg/util/webhook/serviceresolver.go
generated
vendored
Normal file
46
vendor/k8s.io/apiserver/pkg/util/webhook/serviceresolver.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// ServiceResolver knows how to convert a service reference into an actual location.
|
||||
type ServiceResolver interface {
|
||||
ResolveEndpoint(namespace, name string) (*url.URL, error)
|
||||
}
|
||||
|
||||
type defaultServiceResolver struct{}
|
||||
|
||||
// NewDefaultServiceResolver creates a new default server resolver.
|
||||
func NewDefaultServiceResolver() ServiceResolver {
|
||||
return &defaultServiceResolver{}
|
||||
}
|
||||
|
||||
// ResolveEndpoint constructs a service URL from a given namespace and name
|
||||
// note that the name and namespace are required and by default all created addresses use HTTPS scheme.
|
||||
// for example:
|
||||
// name=ross namespace=andromeda resolves to https://ross.andromeda.svc:443
|
||||
func (sr defaultServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) {
|
||||
if len(name) == 0 || len(namespace) == 0 {
|
||||
return nil, errors.New("cannot resolve an empty service name or namespace")
|
||||
}
|
||||
return &url.URL{Scheme: "https", Host: fmt.Sprintf("%s.%s.svc:443", name, namespace)}, nil
|
||||
}
|
101
vendor/k8s.io/apiserver/pkg/util/webhook/validation.go
generated
vendored
Normal file
101
vendor/k8s.io/apiserver/pkg/util/webhook/validation.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// ValidateWebhookURL validates webhook's URL.
|
||||
func ValidateWebhookURL(fldPath *field.Path, URL string, forceHttps bool) field.ErrorList {
|
||||
var allErrors field.ErrorList
|
||||
const form = "; desired format: https://host[/path]"
|
||||
if u, err := url.Parse(URL); err != nil {
|
||||
allErrors = append(allErrors, field.Required(fldPath, "url must be a valid URL: "+err.Error()+form))
|
||||
} else {
|
||||
if forceHttps && u.Scheme != "https" {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath, u.Scheme, "'https' is the only allowed URL scheme"+form))
|
||||
}
|
||||
if len(u.Host) == 0 {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath, u.Host, "host must be provided"+form))
|
||||
}
|
||||
if u.User != nil {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath, u.User.String(), "user information is not permitted in the URL"))
|
||||
}
|
||||
if len(u.Fragment) != 0 {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath, u.Fragment, "fragments are not permitted in the URL"))
|
||||
}
|
||||
if len(u.RawQuery) != 0 {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath, u.RawQuery, "query parameters are not permitted in the URL"))
|
||||
}
|
||||
}
|
||||
return allErrors
|
||||
}
|
||||
|
||||
func ValidateWebhookService(fldPath *field.Path, namespace, name string, path *string) field.ErrorList {
|
||||
var allErrors field.ErrorList
|
||||
|
||||
if len(name) == 0 {
|
||||
allErrors = append(allErrors, field.Required(fldPath.Child("name"), "service name is required"))
|
||||
}
|
||||
|
||||
if len(namespace) == 0 {
|
||||
allErrors = append(allErrors, field.Required(fldPath.Child("namespace"), "service namespace is required"))
|
||||
}
|
||||
|
||||
if path == nil {
|
||||
return allErrors
|
||||
}
|
||||
|
||||
// TODO: replace below with url.Parse + verifying that host is empty?
|
||||
|
||||
urlPath := *path
|
||||
if urlPath == "/" || len(urlPath) == 0 {
|
||||
return allErrors
|
||||
}
|
||||
if urlPath == "//" {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, "segment[0] may not be empty"))
|
||||
return allErrors
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(urlPath, "/") {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, "must start with a '/'"))
|
||||
}
|
||||
|
||||
urlPathToCheck := urlPath[1:]
|
||||
if strings.HasSuffix(urlPathToCheck, "/") {
|
||||
urlPathToCheck = urlPathToCheck[:len(urlPathToCheck)-1]
|
||||
}
|
||||
steps := strings.Split(urlPathToCheck, "/")
|
||||
for i, step := range steps {
|
||||
if len(step) == 0 {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, fmt.Sprintf("segment[%d] may not be empty", i)))
|
||||
continue
|
||||
}
|
||||
failures := validation.IsDNS1123Subdomain(step)
|
||||
for _, failure := range failures {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, fmt.Sprintf("segment[%d]: %v", i, failure)))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrors
|
||||
}
|
120
vendor/k8s.io/apiserver/pkg/util/webhook/webhook.go
generated
vendored
Normal file
120
vendor/k8s.io/apiserver/pkg/util/webhook/webhook.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package webhook implements a generic HTTP webhook plugin.
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// defaultRequestTimeout is set for all webhook request. This is the absolute
|
||||
// timeout of the HTTP request, including reading the response body.
|
||||
const defaultRequestTimeout = 30 * time.Second
|
||||
|
||||
type GenericWebhook struct {
|
||||
RestClient *rest.RESTClient
|
||||
InitialBackoff time.Duration
|
||||
}
|
||||
|
||||
// NewGenericWebhook creates a new GenericWebhook from the provided kubeconfig file.
|
||||
func NewGenericWebhook(scheme *runtime.Scheme, codecFactory serializer.CodecFactory, kubeConfigFile string, groupVersions []schema.GroupVersion, initialBackoff time.Duration) (*GenericWebhook, error) {
|
||||
return newGenericWebhook(scheme, codecFactory, kubeConfigFile, groupVersions, initialBackoff, defaultRequestTimeout)
|
||||
}
|
||||
|
||||
func newGenericWebhook(scheme *runtime.Scheme, codecFactory serializer.CodecFactory, kubeConfigFile string, groupVersions []schema.GroupVersion, initialBackoff, requestTimeout time.Duration) (*GenericWebhook, error) {
|
||||
for _, groupVersion := range groupVersions {
|
||||
if !scheme.IsVersionRegistered(groupVersion) {
|
||||
return nil, fmt.Errorf("webhook plugin requires enabling extension resource: %s", groupVersion)
|
||||
}
|
||||
}
|
||||
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
loadingRules.ExplicitPath = kubeConfigFile
|
||||
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
|
||||
|
||||
clientConfig, err := loader.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Kubeconfigs can't set a timeout, this can only be set through a command line flag.
|
||||
//
|
||||
// https://github.com/kubernetes/client-go/blob/master/tools/clientcmd/overrides.go
|
||||
//
|
||||
// Set this to something reasonable so request to webhooks don't hang forever.
|
||||
clientConfig.Timeout = requestTimeout
|
||||
|
||||
codec := codecFactory.LegacyCodec(groupVersions...)
|
||||
clientConfig.ContentConfig.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec})
|
||||
|
||||
restClient, err := rest.UnversionedRESTClientFor(clientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &GenericWebhook{restClient, initialBackoff}, nil
|
||||
}
|
||||
|
||||
// WithExponentialBackoff will retry webhookFn() up to 5 times with exponentially increasing backoff when
|
||||
// it returns an error for which apierrors.SuggestsClientDelay() or apierrors.IsInternalError() returns true.
|
||||
func (g *GenericWebhook) WithExponentialBackoff(webhookFn func() rest.Result) rest.Result {
|
||||
var result rest.Result
|
||||
WithExponentialBackoff(g.InitialBackoff, func() error {
|
||||
result = webhookFn()
|
||||
return result.Error()
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// WithExponentialBackoff will retry webhookFn() up to 5 times with exponentially increasing backoff when
|
||||
// it returns an error for which apierrors.SuggestsClientDelay() or apierrors.IsInternalError() returns true.
|
||||
func WithExponentialBackoff(initialBackoff time.Duration, webhookFn func() error) error {
|
||||
backoff := wait.Backoff{
|
||||
Duration: initialBackoff,
|
||||
Factor: 1.5,
|
||||
Jitter: 0.2,
|
||||
Steps: 5,
|
||||
}
|
||||
|
||||
var err error
|
||||
wait.ExponentialBackoff(backoff, func() (bool, error) {
|
||||
err = webhookFn()
|
||||
// these errors indicate a transient error that should be retried.
|
||||
if net.IsConnectionReset(err) || apierrors.IsInternalError(err) || apierrors.IsTimeout(err) || apierrors.IsTooManyRequests(err) {
|
||||
return false, nil
|
||||
}
|
||||
// if the error sends the Retry-After header, we respect it as an explicit confirmation we should retry.
|
||||
if _, shouldRetry := apierrors.SuggestsClientDelay(err); shouldRetry {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
return err
|
||||
}
|
Reference in New Issue
Block a user