Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
d6e1140b
Commit
d6e1140b
authored
May 24, 2017
by
Daniel Smith
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement dynamic admission webhooks
Also fix a bug in rest client
parent
f4403d26
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
414 additions
and
582 deletions
+414
-582
options.go
cmd/kube-apiserver/app/options/options.go
+12
-4
server.go
cmd/kube-apiserver/app/server.go
+14
-0
.linted_packages
hack/.linted_packages
+1
-0
types.go
pkg/apis/admission/v1alpha1/types.go
+1
-1
init_test.go
pkg/kubeapiserver/admission/init_test.go
+72
-7
initializer.go
pkg/kubeapiserver/admission/initializer.go
+93
-11
apply_test.go
pkg/kubectl/cmd/apply_test.go
+1
-2
create_deployment_test.go
pkg/kubectl/cmd/create_deployment_test.go
+1
-1
run_test.go
pkg/kubectl/cmd/run_test.go
+1
-1
import_known_versions_test.go
pkg/master/import_known_versions_test.go
+1
-0
admission.go
plugin/pkg/admission/webhook/admission.go
+139
-81
admission_test.go
plugin/pkg/admission/webhook/admission_test.go
+0
-0
config.go
plugin/pkg/admission/webhook/config.go
+0
-120
config_test.go
plugin/pkg/admission/webhook/config_test.go
+0
-220
rules.go
plugin/pkg/admission/webhook/rules.go
+52
-56
rules_test.go
plugin/pkg/admission/webhook/rules_test.go
+0
-0
types.go
plugin/pkg/admission/webhook/types.go
+0
-69
group_version.go
...src/k8s.io/apimachinery/pkg/apis/meta/v1/group_version.go
+9
-9
request.go
staging/src/k8s.io/client-go/rest/request.go
+3
-0
request_test.go
staging/src/k8s.io/client-go/rest/request_test.go
+10
-0
etcd_storage_path_test.go
test/integration/etcd/etcd_storage_path_test.go
+4
-0
No files found.
cmd/kube-apiserver/app/options/options.go
View file @
d6e1140b
...
@@ -214,10 +214,18 @@ func (s *ServerRunOptions) AddFlags(fs *pflag.FlagSet) {
...
@@ -214,10 +214,18 @@ func (s *ServerRunOptions) AddFlags(fs *pflag.FlagSet) {
"e.g., setting empty UID in update request to its existing value. This flag can be turned off "
+
"e.g., setting empty UID in update request to its existing value. This flag can be turned off "
+
"after we fix all the clients that send malformed updates."
)
"after we fix all the clients that send malformed updates."
)
fs
.
StringVar
(
&
s
.
ProxyClientCertFile
,
"proxy-client-cert-file"
,
s
.
ProxyClientCertFile
,
fs
.
StringVar
(
&
s
.
ProxyClientCertFile
,
"proxy-client-cert-file"
,
s
.
ProxyClientCertFile
,
""
+
"client certificate used to prove the identity of the aggragator or kube-apiserver when it proxies requests to a user api-server"
)
"Client certificate used to prove the identity of the aggregator or kube-apiserver "
+
fs
.
StringVar
(
&
s
.
ProxyClientKeyFile
,
"proxy-client-key-file"
,
s
.
ProxyClientKeyFile
,
"when it must call out during a request. This includes proxying requests to a user "
+
"client certificate key used to prove the identity of the aggragator or kube-apiserver when it proxies requests to a user api-server"
)
"api-server and calling out to webhook admission plugins. It is expected that this "
+
"cert includes a signature from the CA in the --requestheader-client-ca-file flag. "
+
"That CA is published in the 'extension-apiserver-authentication' configmap in "
+
"the kube-system namespace. Components recieving calls from kube-aggregator should "
+
"use that CA to perform their half of the mutual TLS verification."
)
fs
.
StringVar
(
&
s
.
ProxyClientKeyFile
,
"proxy-client-key-file"
,
s
.
ProxyClientKeyFile
,
""
+
"Private key for the client certificate used to prove the identity of the aggregator or kube-apiserver "
+
"when it must call out during a request. This includes proxying requests to a user "
+
"api-server and calling out to webhook admission plugins."
)
fs
.
BoolVar
(
&
s
.
EnableAggregatorRouting
,
"enable-aggregator-routing"
,
s
.
EnableAggregatorRouting
,
fs
.
BoolVar
(
&
s
.
EnableAggregatorRouting
,
"enable-aggregator-routing"
,
s
.
EnableAggregatorRouting
,
"Turns on aggregator routing requests to endoints IP rather than cluster IP."
)
"Turns on aggregator routing requests to endoints IP rather than cluster IP."
)
...
...
cmd/kube-apiserver/app/server.go
View file @
d6e1140b
...
@@ -424,6 +424,20 @@ func BuildAdmissionPluginInitializer(s *options.ServerRunOptions, client interna
...
@@ -424,6 +424,20 @@ func BuildAdmissionPluginInitializer(s *options.ServerRunOptions, client interna
quotaRegistry
:=
quotainstall
.
NewRegistry
(
nil
,
nil
)
quotaRegistry
:=
quotainstall
.
NewRegistry
(
nil
,
nil
)
pluginInitializer
:=
kubeapiserveradmission
.
NewPluginInitializer
(
client
,
sharedInformers
,
apiAuthorizer
,
cloudConfig
,
restMapper
,
quotaRegistry
)
pluginInitializer
:=
kubeapiserveradmission
.
NewPluginInitializer
(
client
,
sharedInformers
,
apiAuthorizer
,
cloudConfig
,
restMapper
,
quotaRegistry
)
// Read client cert/key for plugins that need to make calls out
if
len
(
s
.
ProxyClientCertFile
)
>
0
&&
len
(
s
.
ProxyClientKeyFile
)
>
0
{
certBytes
,
err
:=
ioutil
.
ReadFile
(
s
.
ProxyClientCertFile
)
if
err
!=
nil
{
return
nil
,
err
}
keyBytes
,
err
:=
ioutil
.
ReadFile
(
s
.
ProxyClientKeyFile
)
if
err
!=
nil
{
return
nil
,
err
}
pluginInitializer
=
pluginInitializer
.
SetClientCert
(
certBytes
,
keyBytes
)
}
return
pluginInitializer
,
nil
return
pluginInitializer
,
nil
}
}
...
...
hack/.linted_packages
View file @
d6e1140b
...
@@ -68,6 +68,7 @@ pkg/api/v1/node
...
@@ -68,6 +68,7 @@ pkg/api/v1/node
pkg/api/v1/service
pkg/api/v1/service
pkg/apis/abac/v0
pkg/apis/abac/v0
pkg/apis/abac/v1beta1
pkg/apis/abac/v1beta1
pkg/apis/admission/install
pkg/apis/admissionregistration/install
pkg/apis/admissionregistration/install
pkg/apis/apps/install
pkg/apis/apps/install
pkg/apis/apps/v1beta1
pkg/apis/apps/v1beta1
...
...
pkg/apis/admission/v1alpha1/types.go
View file @
d6e1140b
...
@@ -30,7 +30,7 @@ type AdmissionReview struct {
...
@@ -30,7 +30,7 @@ type AdmissionReview struct {
// Since this admission controller is non-mutating the webhook should avoid setting this in its response to avoid the
// Since this admission controller is non-mutating the webhook should avoid setting this in its response to avoid the
// cost of deserializing it.
// cost of deserializing it.
// +optional
// +optional
Spec
AdmissionReviewSpec
`json:"spec" protobuf:"bytes,1,opt,name=spec"`
Spec
AdmissionReviewSpec
`json:"spec
,omitempty
" protobuf:"bytes,1,opt,name=spec"`
// Status is filled in by the webhook and indicates whether the admission request should be permitted.
// Status is filled in by the webhook and indicates whether the admission request should be permitted.
// +optional
// +optional
Status
AdmissionReviewStatus
`json:"status,omitempty" protobuf:"bytes,2,opt,name=status"`
Status
AdmissionReviewStatus
`json:"status,omitempty" protobuf:"bytes,2,opt,name=status"`
...
...
pkg/kubeapiserver/admission/init_test.go
View file @
d6e1140b
...
@@ -17,10 +17,12 @@ limitations under the License.
...
@@ -17,10 +17,12 @@ limitations under the License.
package
admission
package
admission
import
(
import
(
"net/url"
"testing"
"testing"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
)
)
// TestAuthorizer is a testing struct for testing that fulfills the authorizer interface.
// TestAuthorizer is a testing struct for testing that fulfills the authorizer interface.
...
@@ -32,18 +34,22 @@ func (t *TestAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, re
...
@@ -32,18 +34,22 @@ func (t *TestAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, re
var
_
authorizer
.
Authorizer
=
&
TestAuthorizer
{}
var
_
authorizer
.
Authorizer
=
&
TestAuthorizer
{}
type
doNothingAdmission
struct
{}
func
(
doNothingAdmission
)
Admit
(
a
admission
.
Attributes
)
error
{
return
nil
}
func
(
doNothingAdmission
)
Handles
(
o
admission
.
Operation
)
bool
{
return
false
}
func
(
doNothingAdmission
)
Validate
()
error
{
return
nil
}
// WantAuthorizerAdmission is a testing struct that fulfills the WantsAuthorizer
// WantAuthorizerAdmission is a testing struct that fulfills the WantsAuthorizer
// interface.
// interface.
type
WantAuthorizerAdmission
struct
{
type
WantAuthorizerAdmission
struct
{
doNothingAdmission
auth
authorizer
.
Authorizer
auth
authorizer
.
Authorizer
}
}
func
(
self
*
WantAuthorizerAdmission
)
SetAuthorizer
(
a
authorizer
.
Authorizer
)
{
func
(
self
*
WantAuthorizerAdmission
)
SetAuthorizer
(
a
authorizer
.
Authorizer
)
{
self
.
auth
=
a
self
.
auth
=
a
}
}
func
(
self
*
WantAuthorizerAdmission
)
Admit
(
a
admission
.
Attributes
)
error
{
return
nil
}
func
(
self
*
WantAuthorizerAdmission
)
Handles
(
o
admission
.
Operation
)
bool
{
return
false
}
func
(
self
*
WantAuthorizerAdmission
)
Validate
()
error
{
return
nil
}
var
_
admission
.
Interface
=
&
WantAuthorizerAdmission
{}
var
_
admission
.
Interface
=
&
WantAuthorizerAdmission
{}
var
_
WantsAuthorizer
=
&
WantAuthorizerAdmission
{}
var
_
WantsAuthorizer
=
&
WantAuthorizerAdmission
{}
...
@@ -60,6 +66,7 @@ func TestWantsAuthorizer(t *testing.T) {
...
@@ -60,6 +66,7 @@ func TestWantsAuthorizer(t *testing.T) {
}
}
type
WantsCloudConfigAdmissionPlugin
struct
{
type
WantsCloudConfigAdmissionPlugin
struct
{
doNothingAdmission
cloudConfig
[]
byte
cloudConfig
[]
byte
}
}
...
@@ -67,10 +74,6 @@ func (self *WantsCloudConfigAdmissionPlugin) SetCloudConfig(cloudConfig []byte)
...
@@ -67,10 +74,6 @@ func (self *WantsCloudConfigAdmissionPlugin) SetCloudConfig(cloudConfig []byte)
self
.
cloudConfig
=
cloudConfig
self
.
cloudConfig
=
cloudConfig
}
}
func
(
self
*
WantsCloudConfigAdmissionPlugin
)
Admit
(
a
admission
.
Attributes
)
error
{
return
nil
}
func
(
self
*
WantsCloudConfigAdmissionPlugin
)
Handles
(
o
admission
.
Operation
)
bool
{
return
false
}
func
(
self
*
WantsCloudConfigAdmissionPlugin
)
Validate
()
error
{
return
nil
}
func
TestCloudConfigAdmissionPlugin
(
t
*
testing
.
T
)
{
func
TestCloudConfigAdmissionPlugin
(
t
*
testing
.
T
)
{
cloudConfig
:=
[]
byte
(
"cloud-configuration"
)
cloudConfig
:=
[]
byte
(
"cloud-configuration"
)
initializer
:=
NewPluginInitializer
(
nil
,
nil
,
&
TestAuthorizer
{},
cloudConfig
,
nil
,
nil
)
initializer
:=
NewPluginInitializer
(
nil
,
nil
,
&
TestAuthorizer
{},
cloudConfig
,
nil
,
nil
)
...
@@ -81,3 +84,65 @@ func TestCloudConfigAdmissionPlugin(t *testing.T) {
...
@@ -81,3 +84,65 @@ func TestCloudConfigAdmissionPlugin(t *testing.T) {
t
.
Errorf
(
"Expected cloud config to be initialized but found nil"
)
t
.
Errorf
(
"Expected cloud config to be initialized but found nil"
)
}
}
}
}
type
fakeServiceResolver
struct
{}
func
(
f
*
fakeServiceResolver
)
ResolveEndpoint
(
namespace
,
name
string
)
(
*
url
.
URL
,
error
)
{
return
nil
,
nil
}
type
serviceWanter
struct
{
doNothingAdmission
got
ServiceResolver
}
func
(
s
*
serviceWanter
)
SetServiceResolver
(
sr
ServiceResolver
)
{
s
.
got
=
sr
}
func
TestWantsServiceResolver
(
t
*
testing
.
T
)
{
sw
:=
&
serviceWanter
{}
fsr
:=
&
fakeServiceResolver
{}
i
:=
&
PluginInitializer
{}
i
.
SetServiceResolver
(
fsr
)
.
Initialize
(
sw
)
if
got
,
ok
:=
sw
.
got
.
(
*
fakeServiceResolver
);
!
ok
||
got
!=
fsr
{
t
.
Errorf
(
"plumbing fail - %v %v#"
,
ok
,
got
)
}
}
type
clientCertWanter
struct
{
doNothingAdmission
gotCert
,
gotKey
[]
byte
}
func
(
s
*
clientCertWanter
)
SetClientCert
(
cert
,
key
[]
byte
)
{
s
.
gotCert
,
s
.
gotKey
=
cert
,
key
}
func
TestWantsClientCert
(
t
*
testing
.
T
)
{
i
:=
&
PluginInitializer
{}
ccw
:=
&
clientCertWanter
{}
i
.
SetClientCert
([]
byte
(
"cert"
),
[]
byte
(
"key"
))
.
Initialize
(
ccw
)
if
string
(
ccw
.
gotCert
)
!=
"cert"
||
string
(
ccw
.
gotKey
)
!=
"key"
{
t
.
Errorf
(
"plumbing fail - %v %v"
,
ccw
.
gotCert
,
ccw
.
gotKey
)
}
}
type
fakeHookSource
struct
{}
func
(
f
*
fakeHookSource
)
List
()
([]
admissionregistration
.
ExternalAdmissionHook
,
error
)
{
return
nil
,
nil
}
type
hookSourceWanter
struct
{
doNothingAdmission
got
WebhookSource
}
func
(
s
*
hookSourceWanter
)
SetWebhookSource
(
w
WebhookSource
)
{
s
.
got
=
w
}
func
TestWantsWebhookSource
(
t
*
testing
.
T
)
{
hsw
:=
&
hookSourceWanter
{}
fhs
:=
&
fakeHookSource
{}
i
:=
&
PluginInitializer
{}
i
.
SetWebhookSource
(
fhs
)
.
Initialize
(
hsw
)
if
got
,
ok
:=
hsw
.
got
.
(
*
fakeHookSource
);
!
ok
||
got
!=
fhs
{
t
.
Errorf
(
"plumbing fail - %v %v#"
,
ok
,
got
)
}
}
pkg/kubeapiserver/admission/initializer.go
View file @
d6e1140b
...
@@ -17,9 +17,12 @@ limitations under the License.
...
@@ -17,9 +17,12 @@ limitations under the License.
package
admission
package
admission
import
(
import
(
"net/url"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
informers
"k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
informers
"k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
"k8s.io/kubernetes/pkg/quota"
"k8s.io/kubernetes/pkg/quota"
...
@@ -61,25 +64,62 @@ type WantsQuotaRegistry interface {
...
@@ -61,25 +64,62 @@ type WantsQuotaRegistry interface {
admission
.
Validator
admission
.
Validator
}
}
type
pluginInitializer
struct
{
// WantsServiceResolver defines a fuction that accepts a ServiceResolver for
internalClient
internalclientset
.
Interface
// admission plugins that need to make calls to services.
informers
informers
.
SharedInformerFactory
type
WantsServiceResolver
interface
{
authorizer
authorizer
.
Authorizer
SetServiceResolver
(
ServiceResolver
)
cloudConfig
[]
byte
}
restMapper
meta
.
RESTMapper
quotaRegistry
quota
.
Registry
// WantsClientCert defines a fuction that accepts a cert & key for admission
// plugins that need to make calls and prove their identity.
type
WantsClientCert
interface
{
SetClientCert
(
cert
,
key
[]
byte
)
}
// WantsWebhookSource defines a function that accepts a webhook lister for the
// dynamic webhook plugin.
type
WantsWebhookSource
interface
{
SetWebhookSource
(
WebhookSource
)
}
// ServiceResolver knows how to convert a service reference into an actual
// location.
type
ServiceResolver
interface
{
ResolveEndpoint
(
namespace
,
name
string
)
(
*
url
.
URL
,
error
)
}
// WebhookSource can list dynamic webhook plugins.
type
WebhookSource
interface
{
List
()
([]
admissionregistration
.
ExternalAdmissionHook
,
error
)
}
}
var
_
admission
.
PluginInitializer
=
pluginInitializer
{}
type
PluginInitializer
struct
{
internalClient
internalclientset
.
Interface
informers
informers
.
SharedInformerFactory
authorizer
authorizer
.
Authorizer
cloudConfig
[]
byte
restMapper
meta
.
RESTMapper
quotaRegistry
quota
.
Registry
serviceResolver
ServiceResolver
webhookSource
WebhookSource
// for proving we are apiserver in call-outs
clientCert
[]
byte
clientKey
[]
byte
}
var
_
admission
.
PluginInitializer
=
&
PluginInitializer
{}
// NewPluginInitializer constructs new instance of PluginInitializer
// NewPluginInitializer constructs new instance of PluginInitializer
// TODO: switch these parameters to use the builder pattern or just make them
// all public, this construction method is pointless boilerplate.
func
NewPluginInitializer
(
internalClient
internalclientset
.
Interface
,
func
NewPluginInitializer
(
internalClient
internalclientset
.
Interface
,
sharedInformers
informers
.
SharedInformerFactory
,
sharedInformers
informers
.
SharedInformerFactory
,
authz
authorizer
.
Authorizer
,
authz
authorizer
.
Authorizer
,
cloudConfig
[]
byte
,
cloudConfig
[]
byte
,
restMapper
meta
.
RESTMapper
,
restMapper
meta
.
RESTMapper
,
quotaRegistry
quota
.
Registry
)
admission
.
PluginInitializer
{
quotaRegistry
quota
.
Registry
)
*
PluginInitializer
{
return
p
luginInitializer
{
return
&
P
luginInitializer
{
internalClient
:
internalClient
,
internalClient
:
internalClient
,
informers
:
sharedInformers
,
informers
:
sharedInformers
,
authorizer
:
authz
,
authorizer
:
authz
,
...
@@ -89,9 +129,30 @@ func NewPluginInitializer(internalClient internalclientset.Interface,
...
@@ -89,9 +129,30 @@ func NewPluginInitializer(internalClient internalclientset.Interface,
}
}
}
}
// SetServiceResolver sets the service resolver which is needed by some plugins.
func
(
i
*
PluginInitializer
)
SetServiceResolver
(
s
ServiceResolver
)
*
PluginInitializer
{
i
.
serviceResolver
=
s
return
i
}
// SetClientCert sets the client cert & key (identity used for calling out to
// web hooks) which is needed by some plugins.
func
(
i
*
PluginInitializer
)
SetClientCert
(
cert
,
key
[]
byte
)
*
PluginInitializer
{
i
.
clientCert
=
cert
i
.
clientKey
=
key
return
i
}
// SetWebhookSource sets the webhook source-- admittedly this is probably
// specific to the external admission hook plugin.
func
(
i
*
PluginInitializer
)
SetWebhookSource
(
w
WebhookSource
)
*
PluginInitializer
{
i
.
webhookSource
=
w
return
i
}
// Initialize checks the initialization interfaces implemented by each plugin
// Initialize checks the initialization interfaces implemented by each plugin
// and provide the appropriate initialization data
// and provide the appropriate initialization data
func
(
i
p
luginInitializer
)
Initialize
(
plugin
admission
.
Interface
)
{
func
(
i
*
P
luginInitializer
)
Initialize
(
plugin
admission
.
Interface
)
{
if
wants
,
ok
:=
plugin
.
(
WantsInternalKubeClientSet
);
ok
{
if
wants
,
ok
:=
plugin
.
(
WantsInternalKubeClientSet
);
ok
{
wants
.
SetInternalKubeClientSet
(
i
.
internalClient
)
wants
.
SetInternalKubeClientSet
(
i
.
internalClient
)
}
}
...
@@ -115,4 +176,25 @@ func (i pluginInitializer) Initialize(plugin admission.Interface) {
...
@@ -115,4 +176,25 @@ func (i pluginInitializer) Initialize(plugin admission.Interface) {
if
wants
,
ok
:=
plugin
.
(
WantsQuotaRegistry
);
ok
{
if
wants
,
ok
:=
plugin
.
(
WantsQuotaRegistry
);
ok
{
wants
.
SetQuotaRegistry
(
i
.
quotaRegistry
)
wants
.
SetQuotaRegistry
(
i
.
quotaRegistry
)
}
}
if
wants
,
ok
:=
plugin
.
(
WantsServiceResolver
);
ok
{
if
i
.
serviceResolver
==
nil
{
panic
(
"An admission plugin wants the service resolver, but it was not provided."
)
}
wants
.
SetServiceResolver
(
i
.
serviceResolver
)
}
if
wants
,
ok
:=
plugin
.
(
WantsClientCert
);
ok
{
if
i
.
clientCert
==
nil
||
i
.
clientKey
==
nil
{
panic
(
"An admission plugin wants a client cert/key, but they were not provided."
)
}
wants
.
SetClientCert
(
i
.
clientCert
,
i
.
clientKey
)
}
if
wants
,
ok
:=
plugin
.
(
WantsWebhookSource
);
ok
{
if
i
.
webhookSource
==
nil
{
panic
(
"An admission plugin wants a webhook source, but it was not provided."
)
}
wants
.
SetWebhookSource
(
i
.
webhookSource
)
}
}
}
pkg/kubectl/cmd/apply_test.go
View file @
d6e1140b
...
@@ -987,9 +987,8 @@ func TestRunApplySetLastApplied(t *testing.T) {
...
@@ -987,9 +987,8 @@ func TestRunApplySetLastApplied(t *testing.T) {
if
buf
.
String
()
!=
test
.
expectedOut
{
if
buf
.
String
()
!=
test
.
expectedOut
{
t
.
Fatalf
(
"%s: unexpected output: %s
\n
expected: %s"
,
test
.
name
,
buf
.
String
(),
test
.
expectedOut
)
t
.
Fatalf
(
"%s: unexpected output: %s
\n
expected: %s"
,
test
.
name
,
buf
.
String
(),
test
.
expectedOut
)
}
}
}
}
cmdutil
.
BehaviorOnFatal
(
func
(
str
string
,
code
int
)
{})
}
}
func
checkPatchString
(
t
*
testing
.
T
,
req
*
http
.
Request
)
{
func
checkPatchString
(
t
*
testing
.
T
,
req
*
http
.
Request
)
{
...
...
pkg/kubectl/cmd/create_deployment_test.go
View file @
d6e1140b
...
@@ -39,7 +39,7 @@ func TestCreateDeployment(t *testing.T) {
...
@@ -39,7 +39,7 @@ func TestCreateDeployment(t *testing.T) {
Client
:
fake
.
CreateHTTPClient
(
func
(
req
*
http
.
Request
)
(
*
http
.
Response
,
error
)
{
Client
:
fake
.
CreateHTTPClient
(
func
(
req
*
http
.
Request
)
(
*
http
.
Response
,
error
)
{
return
&
http
.
Response
{
return
&
http
.
Response
{
StatusCode
:
http
.
StatusOK
,
StatusCode
:
http
.
StatusOK
,
Body
:
ioutil
.
NopCloser
(
&
bytes
.
Buffer
{}
),
Body
:
ioutil
.
NopCloser
(
bytes
.
NewBuffer
([]
byte
(
"{}"
))
),
},
nil
},
nil
}),
}),
}
}
...
...
pkg/kubectl/cmd/run_test.go
View file @
d6e1140b
...
@@ -171,7 +171,7 @@ func TestRunArgsFollowDashRules(t *testing.T) {
...
@@ -171,7 +171,7 @@ func TestRunArgsFollowDashRules(t *testing.T) {
}
else
{
}
else
{
return
&
http
.
Response
{
return
&
http
.
Response
{
StatusCode
:
http
.
StatusOK
,
StatusCode
:
http
.
StatusOK
,
Body
:
ioutil
.
NopCloser
(
&
bytes
.
Buffer
{}
),
Body
:
ioutil
.
NopCloser
(
bytes
.
NewBuffer
([]
byte
(
"{}"
))
),
},
nil
},
nil
}
}
}),
}),
...
...
pkg/master/import_known_versions_test.go
View file @
d6e1140b
...
@@ -85,6 +85,7 @@ var typesAllowedTags = map[reflect.Type]bool{
...
@@ -85,6 +85,7 @@ var typesAllowedTags = map[reflect.Type]bool{
reflect
.
TypeOf
(
metav1
.
DeleteOptions
{})
:
true
,
reflect
.
TypeOf
(
metav1
.
DeleteOptions
{})
:
true
,
reflect
.
TypeOf
(
metav1
.
GroupVersionKind
{})
:
true
,
reflect
.
TypeOf
(
metav1
.
GroupVersionKind
{})
:
true
,
reflect
.
TypeOf
(
metav1
.
GroupVersionResource
{})
:
true
,
reflect
.
TypeOf
(
metav1
.
GroupVersionResource
{})
:
true
,
reflect
.
TypeOf
(
metav1
.
Status
{})
:
true
,
}
}
func
ensureNoTags
(
t
*
testing
.
T
,
gvk
schema
.
GroupVersionKind
,
tp
reflect
.
Type
,
parents
[]
reflect
.
Type
)
{
func
ensureNoTags
(
t
*
testing
.
T
,
gvk
schema
.
GroupVersionKind
,
tp
reflect
.
Type
,
parents
[]
reflect
.
Type
)
{
...
...
plugin/pkg/admission/webhook/admission.go
View file @
d6e1140b
...
@@ -14,25 +14,31 @@ See the License for the specific language governing permissions and
...
@@ -14,25 +14,31 @@ See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.
*/
*/
// Package webhook
checks a webhook for configured operation admission
// Package webhook
delegates admission checks to dynamically configured webhooks.
package
webhook
package
webhook
import
(
import
(
"
errors
"
"
context
"
"fmt"
"fmt"
"io"
"io"
"sync"
"time"
"github.com/golang/glog"
"github.com/golang/glog"
apierrors
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/util/webhook"
"k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api"
admissionv1alpha1
"k8s.io/kubernetes/pkg/apis/admission/v1alpha1"
admissionv1alpha1
"k8s.io/kubernetes/pkg/apis/admission/v1alpha1"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
admissioninit
"k8s.io/kubernetes/pkg/kubeapiserver/admission"
// install the clientgo admission
y
API for use with api registry
// install the clientgo admission API for use with api registry
_
"k8s.io/kubernetes/pkg/apis/admission/install"
_
"k8s.io/kubernetes/pkg/apis/admission/install"
)
)
...
@@ -42,22 +48,22 @@ var (
...
@@ -42,22 +48,22 @@ var (
}
}
)
)
type
ErrCallingWebhook
struct
{
WebhookName
string
Reason
error
}
func
(
e
*
ErrCallingWebhook
)
Error
()
string
{
if
e
.
Reason
!=
nil
{
return
fmt
.
Sprintf
(
"failed calling admission webhook %q: %v"
,
e
.
WebhookName
,
e
.
Reason
)
}
return
fmt
.
Sprintf
(
"failed calling admission webhook %q; no further details available"
,
e
.
WebhookName
)
}
// Register registers a plugin
// Register registers a plugin
func
Register
(
plugins
*
admission
.
Plugins
)
{
func
Register
(
plugins
*
admission
.
Plugins
)
{
plugins
.
Register
(
"GenericAdmissionWebhook"
,
func
(
configFile
io
.
Reader
)
(
admission
.
Interface
,
error
)
{
plugins
.
Register
(
"GenericAdmissionWebhook"
,
func
(
configFile
io
.
Reader
)
(
admission
.
Interface
,
error
)
{
var
gwhConfig
struct
{
plugin
,
err
:=
NewGenericAdmissionWebhook
()
WebhookConfig
GenericAdmissionWebhookConfig
`json:"webhook"`
}
d
:=
yaml
.
NewYAMLOrJSONDecoder
(
configFile
,
4096
)
err
:=
d
.
Decode
(
&
gwhConfig
)
if
err
!=
nil
{
return
nil
,
err
}
plugin
,
err
:=
NewGenericAdmissionWebhook
(
&
gwhConfig
.
WebhookConfig
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
...
@@ -67,98 +73,150 @@ func Register(plugins *admission.Plugins) {
...
@@ -67,98 +73,150 @@ func Register(plugins *admission.Plugins) {
}
}
// NewGenericAdmissionWebhook returns a generic admission webhook plugin.
// NewGenericAdmissionWebhook returns a generic admission webhook plugin.
func
NewGenericAdmissionWebhook
(
config
*
GenericAdmissionWebhookConfig
)
(
admission
.
Interface
,
error
)
{
func
NewGenericAdmissionWebhook
()
(
*
GenericAdmissionWebhook
,
error
)
{
err
:=
normalizeConfig
(
config
)
if
err
!=
nil
{
return
nil
,
err
}
gw
,
err
:=
webhook
.
NewGenericWebhook
(
api
.
Registry
,
api
.
Codecs
,
config
.
KubeConfigFile
,
groupVersions
,
config
.
RetryBackoff
)
if
err
!=
nil
{
return
nil
,
err
}
return
&
GenericAdmissionWebhook
{
return
&
GenericAdmissionWebhook
{
Handler
:
admission
.
NewHandler
(
admission
.
Connect
,
admission
.
Create
,
admission
.
Delete
,
admission
.
Update
),
Handler
:
admission
.
NewHandler
(
webhook
:
gw
,
admission
.
Connect
,
rules
:
config
.
Rules
,
admission
.
Create
,
admission
.
Delete
,
admission
.
Update
,
),
negotiatedSerializer
:
serializer
.
NegotiatedSerializerWrapper
(
runtime
.
SerializerInfo
{
Serializer
:
api
.
Codecs
.
LegacyCodec
(
admissionv1alpha1
.
SchemeGroupVersion
),
}),
},
nil
},
nil
}
}
// GenericAdmissionWebhook is an implementation of admission.Interface.
// GenericAdmissionWebhook is an implementation of admission.Interface.
type
GenericAdmissionWebhook
struct
{
type
GenericAdmissionWebhook
struct
{
*
admission
.
Handler
*
admission
.
Handler
webhook
*
webhook
.
GenericWebhook
hookSource
admissioninit
.
WebhookSource
rules
[]
Rule
serviceResolver
admissioninit
.
ServiceResolver
negotiatedSerializer
runtime
.
NegotiatedSerializer
clientCert
[]
byte
clientKey
[]
byte
}
var
(
_
=
admissioninit
.
WantsServiceResolver
(
&
GenericAdmissionWebhook
{})
_
=
admissioninit
.
WantsClientCert
(
&
GenericAdmissionWebhook
{})
_
=
admissioninit
.
WantsWebhookSource
(
&
GenericAdmissionWebhook
{})
)
func
(
a
*
GenericAdmissionWebhook
)
SetServiceResolver
(
sr
admissioninit
.
ServiceResolver
)
{
a
.
serviceResolver
=
sr
}
func
(
a
*
GenericAdmissionWebhook
)
SetClientCert
(
cert
,
key
[]
byte
)
{
a
.
clientCert
=
cert
a
.
clientKey
=
key
}
func
(
a
*
GenericAdmissionWebhook
)
SetWebhookSource
(
ws
admissioninit
.
WebhookSource
)
{
a
.
hookSource
=
ws
}
}
// Admit makes an admission decision based on the request attributes.
// Admit makes an admission decision based on the request attributes.
func
(
a
*
GenericAdmissionWebhook
)
Admit
(
attr
admission
.
Attributes
)
(
err
error
)
{
func
(
a
*
GenericAdmissionWebhook
)
Admit
(
attr
admission
.
Attributes
)
error
{
var
matched
*
Rule
hooks
,
err
:=
a
.
hookSource
.
List
()
if
err
!=
nil
{
// Process all declared rules to attempt to find a match
return
fmt
.
Errorf
(
"failed listing hooks: %v"
,
err
)
for
i
,
rule
:=
range
a
.
rules
{
}
if
Matches
(
rule
,
attr
)
{
ctx
:=
context
.
TODO
()
glog
.
V
(
2
)
.
Infof
(
"rule at index %d matched request"
,
i
)
matched
=
&
a
.
rules
[
i
]
errCh
:=
make
(
chan
error
,
len
(
hooks
))
break
wg
:=
sync
.
WaitGroup
{}
}
wg
.
Add
(
len
(
hooks
))
for
i
:=
range
hooks
{
go
func
(
hook
*
admissionregistration
.
ExternalAdmissionHook
)
{
defer
wg
.
Done
()
if
err
:=
a
.
callHook
(
ctx
,
hook
,
attr
);
err
==
nil
{
return
}
else
if
callErr
,
ok
:=
err
.
(
*
ErrCallingWebhook
);
ok
{
glog
.
Warningf
(
"Failed calling webhook %v: %v"
,
hook
.
Name
,
callErr
)
utilruntime
.
HandleError
(
callErr
)
// Since we are failing open to begin with, we do not send an error down the channel
}
else
{
glog
.
Warningf
(
"rejected by webhook %v %t: %v"
,
hook
.
Name
,
err
,
err
)
errCh
<-
err
}
}(
&
hooks
[
i
])
}
}
wg
.
Wait
()
close
(
errCh
)
if
matched
==
nil
{
var
errs
[]
error
glog
.
V
(
2
)
.
Infof
(
"rule explicitly allowed the request: no rule matched the admission request"
)
for
e
:=
range
errCh
{
errs
=
append
(
errs
,
e
)
}
if
len
(
errs
)
==
0
{
return
nil
return
nil
}
}
if
len
(
errs
)
>
1
{
for
i
:=
1
;
i
<
len
(
errs
);
i
++
{
// TODO: merge status errors; until then, just return the first one.
utilruntime
.
HandleError
(
errs
[
i
])
}
}
return
errs
[
0
]
}
// The matched rule skips processing this request
func
(
a
*
GenericAdmissionWebhook
)
callHook
(
ctx
context
.
Context
,
h
*
admissionregistration
.
ExternalAdmissionHook
,
attr
admission
.
Attributes
)
error
{
if
matched
.
Type
==
Skip
{
matches
:=
false
glog
.
V
(
2
)
.
Infof
(
"rule explicitly allowed the request"
)
for
_
,
r
:=
range
h
.
Rules
{
m
:=
RuleMatcher
{
Rule
:
r
,
Attr
:
attr
}
if
m
.
Matches
()
{
matches
=
true
break
}
}
if
!
matches
{
return
nil
return
nil
}
}
// Make the webhook request
// Make the webhook request
request
:=
admissionv1alpha1
.
NewAdmissionReview
(
attr
)
request
:=
admissionv1alpha1
.
NewAdmissionReview
(
attr
)
response
:=
a
.
webhook
.
RestClient
.
Post
()
.
Body
(
&
request
)
.
Do
()
client
,
err
:=
a
.
hookClient
(
h
)
if
err
!=
nil
{
// Handle webhook response
return
&
ErrCallingWebhook
{
WebhookName
:
h
.
Name
,
Reason
:
err
}
if
err
:=
response
.
Error
();
err
!=
nil
{
return
a
.
handleError
(
attr
,
matched
.
FailAction
,
err
)
}
}
if
err
:=
client
.
Post
()
.
Context
(
ctx
)
.
Body
(
&
request
)
.
Do
()
.
Into
(
&
request
);
err
!=
nil
{
var
statusCode
int
return
&
ErrCallingWebhook
{
WebhookName
:
h
.
Name
,
Reason
:
err
}
if
response
.
StatusCode
(
&
statusCode
);
statusCode
<
200
||
statusCode
>=
300
{
return
a
.
handleError
(
attr
,
matched
.
FailAction
,
fmt
.
Errorf
(
"error contacting webhook: %d"
,
statusCode
))
}
}
if
err
:=
response
.
Into
(
&
request
);
err
!=
nil
{
if
request
.
Status
.
Allowed
{
return
a
.
handleError
(
attr
,
matched
.
FailAction
,
err
)
return
nil
}
}
if
!
request
.
Status
.
Allowed
{
if
request
.
Status
.
Result
==
nil
{
if
request
.
Status
.
Result
!=
nil
&&
len
(
request
.
Status
.
Result
.
Reason
)
>
0
{
return
fmt
.
Errorf
(
"admission webhook %q denied the request without explanation"
,
h
.
Name
)
return
a
.
handleError
(
attr
,
Deny
,
fmt
.
Errorf
(
"webhook backend denied the request: %s"
,
request
.
Status
.
Result
.
Reason
))
}
return
a
.
handleError
(
attr
,
Deny
,
errors
.
New
(
"webhook backend denied the request"
))
}
}
// The webhook admission controller DOES NOT allow mutation of the admission request so nothing else is required
return
&
apierrors
.
StatusError
{
ErrStatus
:
*
request
.
Status
.
Result
,
return
nil
}
}
}
func
(
a
*
GenericAdmissionWebhook
)
handleError
(
attr
admission
.
Attributes
,
allowIfErr
FailAction
,
err
error
)
error
{
func
(
a
*
GenericAdmissionWebhook
)
hookClient
(
h
*
admissionregistration
.
ExternalAdmissionHook
)
(
*
rest
.
RESTClient
,
error
)
{
u
,
err
:=
a
.
serviceResolver
.
ResolveEndpoint
(
h
.
ClientConfig
.
Service
.
Namespace
,
h
.
ClientConfig
.
Service
.
Name
)
if
err
!=
nil
{
if
err
!=
nil
{
glog
.
V
(
2
)
.
Infof
(
"error contacting webhook backend: %s"
,
err
)
return
nil
,
err
if
allowIfErr
!=
Allow
{
glog
.
V
(
2
)
.
Infof
(
"resource not allowed due to webhook backend failure: %s"
,
err
)
return
admission
.
NewForbidden
(
attr
,
err
)
}
glog
.
V
(
2
)
.
Infof
(
"resource allowed in spite of webhook backend failure"
)
}
}
return
nil
// TODO: cache these instead of constructing one each time
cfg
:=
&
rest
.
Config
{
Host
:
u
.
Host
,
APIPath
:
u
.
Path
,
TLSClientConfig
:
rest
.
TLSClientConfig
{
CAData
:
h
.
ClientConfig
.
CABundle
,
CertData
:
a
.
clientCert
,
KeyData
:
a
.
clientKey
,
},
UserAgent
:
"kube-apiserver-admission"
,
Timeout
:
30
*
time
.
Second
,
ContentConfig
:
rest
.
ContentConfig
{
NegotiatedSerializer
:
a
.
negotiatedSerializer
,
},
}
return
rest
.
UnversionedRESTClientFor
(
cfg
)
}
}
// TODO: Allow configuring the serialization strategy
plugin/pkg/admission/webhook/admission_test.go
View file @
d6e1140b
This diff is collapsed.
Click to expand it.
plugin/pkg/admission/webhook/config.go
deleted
100644 → 0
View file @
f4403d26
/*
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 checks a webhook for configured operation admission
package
webhook
import
(
"fmt"
"strings"
"time"
"k8s.io/apiserver/pkg/admission"
)
const
(
defaultFailAction
=
Deny
defaultRetryBackoff
=
time
.
Duration
(
500
)
*
time
.
Millisecond
errInvalidFailAction
=
"webhook rule (%d) has an invalid fail action: %s should be either %v or %v"
errInvalidRuleOperation
=
"webhook rule (%d) has an invalid operation (%d): %s"
errInvalidRuleType
=
"webhook rule (%d) has an invalid type: %s should be either %v or %v"
errMissingKubeConfigFile
=
"kubeConfigFile is required"
errOneRuleRequired
=
"webhook requires at least one rule defined"
errRetryBackoffOutOfRange
=
"webhook retry backoff is invalid: %v should be between %v and %v"
minRetryBackoff
=
time
.
Duration
(
1
)
*
time
.
Millisecond
maxRetryBackoff
=
time
.
Duration
(
5
)
*
time
.
Minute
)
func
normalizeConfig
(
config
*
GenericAdmissionWebhookConfig
)
error
{
allowFailAction
:=
string
(
Allow
)
denyFailAction
:=
string
(
Deny
)
connectOperation
:=
string
(
admission
.
Connect
)
createOperation
:=
string
(
admission
.
Create
)
deleteOperation
:=
string
(
admission
.
Delete
)
updateOperation
:=
string
(
admission
.
Update
)
sendRuleType
:=
string
(
Send
)
skipRuleType
:=
string
(
Skip
)
// Validate the kubeConfigFile property is present
if
config
.
KubeConfigFile
==
""
{
return
fmt
.
Errorf
(
errMissingKubeConfigFile
)
}
// Normalize and validate the retry backoff
if
config
.
RetryBackoff
==
0
{
config
.
RetryBackoff
=
defaultRetryBackoff
}
else
{
// Unmarshalling gives nanoseconds so convert to milliseconds
config
.
RetryBackoff
*=
time
.
Millisecond
}
if
config
.
RetryBackoff
<
minRetryBackoff
||
config
.
RetryBackoff
>
maxRetryBackoff
{
return
fmt
.
Errorf
(
errRetryBackoffOutOfRange
,
config
.
RetryBackoff
,
minRetryBackoff
,
maxRetryBackoff
)
}
// Validate that there is at least one rule
if
len
(
config
.
Rules
)
==
0
{
return
fmt
.
Errorf
(
errOneRuleRequired
)
}
for
i
,
rule
:=
range
config
.
Rules
{
// Normalize and validate the fail action
failAction
:=
strings
.
ToUpper
(
string
(
rule
.
FailAction
))
switch
failAction
{
case
""
:
config
.
Rules
[
i
]
.
FailAction
=
defaultFailAction
case
allowFailAction
:
config
.
Rules
[
i
]
.
FailAction
=
Allow
case
denyFailAction
:
config
.
Rules
[
i
]
.
FailAction
=
Deny
default
:
return
fmt
.
Errorf
(
errInvalidFailAction
,
i
,
rule
.
FailAction
,
Allow
,
Deny
)
}
// Normalize and validate the rule operation(s)
for
j
,
operation
:=
range
rule
.
Operations
{
operation
:=
strings
.
ToUpper
(
string
(
operation
))
switch
operation
{
case
connectOperation
:
config
.
Rules
[
i
]
.
Operations
[
j
]
=
admission
.
Connect
case
createOperation
:
config
.
Rules
[
i
]
.
Operations
[
j
]
=
admission
.
Create
case
deleteOperation
:
config
.
Rules
[
i
]
.
Operations
[
j
]
=
admission
.
Delete
case
updateOperation
:
config
.
Rules
[
i
]
.
Operations
[
j
]
=
admission
.
Update
default
:
return
fmt
.
Errorf
(
errInvalidRuleOperation
,
i
,
j
,
rule
.
Operations
[
j
])
}
}
// Normalize and validate the rule type
ruleType
:=
strings
.
ToUpper
(
string
(
rule
.
Type
))
switch
ruleType
{
case
sendRuleType
:
config
.
Rules
[
i
]
.
Type
=
Send
case
skipRuleType
:
config
.
Rules
[
i
]
.
Type
=
Skip
default
:
return
fmt
.
Errorf
(
errInvalidRuleType
,
i
,
rule
.
Type
,
Send
,
Skip
)
}
}
return
nil
}
plugin/pkg/admission/webhook/config_test.go
deleted
100644 → 0
View file @
f4403d26
/*
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"
"reflect"
"testing"
"time"
"k8s.io/apiserver/pkg/admission"
)
func
TestConfigNormalization
(
t
*
testing
.
T
)
{
defaultRules
:=
[]
Rule
{
Rule
{
Type
:
Skip
,
},
}
highRetryBackoff
:=
(
maxRetryBackoff
/
time
.
Millisecond
)
+
(
time
.
Duration
(
1
)
*
time
.
Millisecond
)
kubeConfigFile
:=
"/tmp/kube/config"
lowRetryBackoff
:=
time
.
Duration
(
-
1
)
normalizedValidRules
:=
[]
Rule
{
Rule
{
APIGroups
:
[]
string
{
""
},
FailAction
:
Allow
,
Namespaces
:
[]
string
{
"my-ns"
},
Operations
:
[]
admission
.
Operation
{
admission
.
Connect
,
admission
.
Create
,
admission
.
Delete
,
admission
.
Update
,
},
Resources
:
[]
string
{
"pods"
},
ResourceNames
:
[]
string
{
"my-name"
},
Type
:
Send
,
},
Rule
{
FailAction
:
Deny
,
Type
:
Skip
,
},
}
rawValidRules
:=
[]
Rule
{
Rule
{
APIGroups
:
[]
string
{
""
},
FailAction
:
FailAction
(
"AlLoW"
),
Namespaces
:
[]
string
{
"my-ns"
},
Operations
:
[]
admission
.
Operation
{
admission
.
Operation
(
"connect"
),
admission
.
Operation
(
"CREATE"
),
admission
.
Operation
(
"DeLeTe"
),
admission
.
Operation
(
"UPdaTE"
),
},
Resources
:
[]
string
{
"pods"
},
ResourceNames
:
[]
string
{
"my-name"
},
Type
:
RuleType
(
"SenD"
),
},
Rule
{
FailAction
:
Deny
,
Type
:
Skip
,
},
}
unknownFailAction
:=
FailAction
(
"Unknown"
)
unknownOperation
:=
admission
.
Operation
(
"Unknown"
)
unknownRuleType
:=
RuleType
(
"Allow"
)
tests
:=
[]
struct
{
test
string
rawConfig
GenericAdmissionWebhookConfig
normalizedConfig
GenericAdmissionWebhookConfig
err
error
}{
{
test
:
"kubeConfigFile was not provided (error)"
,
rawConfig
:
GenericAdmissionWebhookConfig
{},
err
:
fmt
.
Errorf
(
errMissingKubeConfigFile
),
},
{
test
:
"retryBackoff was not provided (use default)"
,
rawConfig
:
GenericAdmissionWebhookConfig
{
KubeConfigFile
:
kubeConfigFile
,
Rules
:
defaultRules
,
},
normalizedConfig
:
GenericAdmissionWebhookConfig
{
KubeConfigFile
:
kubeConfigFile
,
RetryBackoff
:
defaultRetryBackoff
,
Rules
:
defaultRules
,
},
},
{
test
:
"retryBackoff was below minimum value (error)"
,
rawConfig
:
GenericAdmissionWebhookConfig
{
KubeConfigFile
:
kubeConfigFile
,
RetryBackoff
:
lowRetryBackoff
,
Rules
:
defaultRules
,
},
err
:
fmt
.
Errorf
(
errRetryBackoffOutOfRange
,
lowRetryBackoff
*
time
.
Millisecond
,
minRetryBackoff
,
maxRetryBackoff
),
},
{
test
:
"retryBackoff was above maximum value (error)"
,
rawConfig
:
GenericAdmissionWebhookConfig
{
KubeConfigFile
:
kubeConfigFile
,
RetryBackoff
:
highRetryBackoff
,
Rules
:
defaultRules
,
},
err
:
fmt
.
Errorf
(
errRetryBackoffOutOfRange
,
highRetryBackoff
*
time
.
Millisecond
,
minRetryBackoff
,
maxRetryBackoff
),
},
{
test
:
"rules should have at least one rule (error)"
,
rawConfig
:
GenericAdmissionWebhookConfig
{
KubeConfigFile
:
kubeConfigFile
,
},
err
:
fmt
.
Errorf
(
errOneRuleRequired
),
},
{
test
:
"fail action was not provided (use default)"
,
rawConfig
:
GenericAdmissionWebhookConfig
{
KubeConfigFile
:
kubeConfigFile
,
Rules
:
[]
Rule
{
Rule
{
Type
:
Skip
,
},
},
},
normalizedConfig
:
GenericAdmissionWebhookConfig
{
KubeConfigFile
:
kubeConfigFile
,
RetryBackoff
:
defaultRetryBackoff
,
Rules
:
[]
Rule
{
Rule
{
FailAction
:
defaultFailAction
,
Type
:
Skip
,
},
},
},
},
{
test
:
"rule has invalid fail action (error)"
,
rawConfig
:
GenericAdmissionWebhookConfig
{
KubeConfigFile
:
kubeConfigFile
,
Rules
:
[]
Rule
{
Rule
{
FailAction
:
unknownFailAction
,
},
},
},
err
:
fmt
.
Errorf
(
errInvalidFailAction
,
0
,
unknownFailAction
,
Allow
,
Deny
),
},
{
test
:
"rule has invalid operation (error)"
,
rawConfig
:
GenericAdmissionWebhookConfig
{
KubeConfigFile
:
kubeConfigFile
,
Rules
:
[]
Rule
{
Rule
{
Operations
:
[]
admission
.
Operation
{
unknownOperation
},
},
},
},
err
:
fmt
.
Errorf
(
errInvalidRuleOperation
,
0
,
0
,
unknownOperation
),
},
{
test
:
"rule has invalid type (error)"
,
rawConfig
:
GenericAdmissionWebhookConfig
{
KubeConfigFile
:
kubeConfigFile
,
Rules
:
[]
Rule
{
Rule
{
Type
:
unknownRuleType
,
},
},
},
err
:
fmt
.
Errorf
(
errInvalidRuleType
,
0
,
unknownRuleType
,
Send
,
Skip
),
},
{
test
:
"valid configuration"
,
rawConfig
:
GenericAdmissionWebhookConfig
{
KubeConfigFile
:
kubeConfigFile
,
Rules
:
rawValidRules
,
},
normalizedConfig
:
GenericAdmissionWebhookConfig
{
KubeConfigFile
:
kubeConfigFile
,
RetryBackoff
:
defaultRetryBackoff
,
Rules
:
normalizedValidRules
,
},
err
:
nil
,
},
}
for
_
,
tt
:=
range
tests
{
err
:=
normalizeConfig
(
&
tt
.
rawConfig
)
if
err
==
nil
{
if
tt
.
err
!=
nil
{
// Ensure that expected errors are produced
t
.
Errorf
(
"%s: expected error but did not produce one"
,
tt
.
test
)
}
else
if
!
reflect
.
DeepEqual
(
tt
.
rawConfig
,
tt
.
normalizedConfig
)
{
// Ensure that valid configurations are structured properly
t
.
Errorf
(
"%s: normalized config mismtach. got: %v expected: %v"
,
tt
.
test
,
tt
.
rawConfig
,
tt
.
normalizedConfig
)
}
}
else
{
if
tt
.
err
==
nil
{
// Ensure that unexpected errors are not produced
t
.
Errorf
(
"%s: unexpected error: %v"
,
tt
.
test
,
err
)
}
else
if
err
!=
nil
&&
tt
.
err
!=
nil
&&
err
.
Error
()
!=
tt
.
err
.
Error
()
{
// Ensure that expected errors are formated properly
t
.
Errorf
(
"%s: error message mismatch. got: '%v' expected: '%v'"
,
tt
.
test
,
err
,
tt
.
err
)
}
}
}
}
plugin/pkg/admission/webhook/rules.go
View file @
d6e1140b
...
@@ -17,46 +17,31 @@ limitations under the License.
...
@@ -17,46 +17,31 @@ limitations under the License.
// Package webhook checks a webhook for configured operation admission
// Package webhook checks a webhook for configured operation admission
package
webhook
package
webhook
import
"k8s.io/apiserver/pkg/admission"
import
(
"strings"
func
indexOf
(
items
[]
string
,
requested
string
)
int
{
"k8s.io/apiserver/pkg/admission"
for
i
,
item
:=
range
items
{
"k8s.io/kubernetes/pkg/apis/admissionregistration"
if
item
==
requested
{
)
return
i
}
}
return
-
1
}
// APIGroupMatches returns if the admission.Attributes matches the rule's API groups
func
APIGroupMatches
(
rule
Rule
,
attr
admission
.
Attributes
)
bool
{
if
len
(
rule
.
APIGroups
)
==
0
{
return
true
}
return
indexOf
(
rule
.
APIGroups
,
attr
.
GetResource
()
.
Group
)
>
-
1
type
RuleMatcher
struct
{
Rule
admissionregistration
.
RuleWithOperations
Attr
admission
.
Attributes
}
}
// Matches returns if the admission.Attributes matches the rule
func
(
r
*
RuleMatcher
)
Matches
()
bool
{
func
Matches
(
rule
Rule
,
attr
admission
.
Attributes
)
bool
{
return
r
.
operation
()
&&
return
APIGroupMatches
(
rule
,
attr
)
&&
r
.
group
()
&&
NamespaceMatches
(
rule
,
attr
)
&&
r
.
version
()
&&
OperationMatches
(
rule
,
attr
)
&&
r
.
resource
()
ResourceMatches
(
rule
,
attr
)
&&
ResourceNamesMatches
(
rule
,
attr
)
}
}
// OperationMatches returns if the admission.Attributes matches the rule's operation
func
exactOrWildcard
(
items
[]
string
,
requested
string
)
bool
{
func
OperationMatches
(
rule
Rule
,
attr
admission
.
Attributes
)
bool
{
for
_
,
item
:=
range
items
{
if
len
(
rule
.
Operations
)
==
0
{
if
item
==
"*"
{
return
true
return
true
}
}
if
item
==
requested
{
aOp
:=
attr
.
GetOperation
()
for
_
,
rOp
:=
range
rule
.
Operations
{
if
aOp
==
rOp
{
return
true
return
true
}
}
}
}
...
@@ -64,35 +49,46 @@ func OperationMatches(rule Rule, attr admission.Attributes) bool {
...
@@ -64,35 +49,46 @@ func OperationMatches(rule Rule, attr admission.Attributes) bool {
return
false
return
false
}
}
// NamespaceMatches returns if the admission.Attributes matches the rule's namespaces
func
(
r
*
RuleMatcher
)
group
()
bool
{
func
NamespaceMatches
(
rule
Rule
,
attr
admission
.
Attributes
)
bool
{
return
exactOrWildcard
(
r
.
Rule
.
APIGroups
,
r
.
Attr
.
GetResource
()
.
Group
)
if
len
(
rule
.
Namespaces
)
==
0
{
}
return
true
}
return
indexOf
(
rule
.
Namespaces
,
attr
.
GetNamespace
())
>
-
1
func
(
r
*
RuleMatcher
)
version
()
bool
{
return
exactOrWildcard
(
r
.
Rule
.
APIVersions
,
r
.
Attr
.
GetResource
()
.
Version
)
}
}
// ResourceMatches returns if the admission.Attributes matches the rule's resource (and optional subresource)
func
(
r
*
RuleMatcher
)
operation
()
bool
{
func
ResourceMatches
(
rule
Rule
,
attr
admission
.
Attributes
)
bool
{
attrOp
:=
r
.
Attr
.
GetOperation
()
if
len
(
rule
.
Resources
)
==
0
{
for
_
,
op
:=
range
r
.
Rule
.
Operations
{
return
true
if
op
==
admissionregistration
.
OperationAll
{
return
true
}
// The constants are the same such that this is a valid cast (and this
// is tested).
if
op
==
admissionregistration
.
OperationType
(
attrOp
)
{
return
true
}
}
}
return
false
}
resource
:=
attr
.
GetResource
()
.
Resource
func
splitResource
(
resSub
string
)
(
res
,
sub
string
)
{
parts
:=
strings
.
SplitN
(
resSub
,
"/"
,
2
)
if
len
(
attr
.
GetSubresource
())
>
0
{
if
len
(
parts
)
==
2
{
re
source
=
attr
.
GetResource
()
.
Resource
+
"/"
+
attr
.
GetSubresource
()
re
turn
parts
[
0
],
parts
[
1
]
}
}
return
parts
[
0
],
""
return
indexOf
(
rule
.
Resources
,
resource
)
>
-
1
}
}
// ResourceNamesMatches returns if the admission.Attributes matches the rule's resource names
func
(
r
*
RuleMatcher
)
resource
()
bool
{
func
ResourceNamesMatches
(
rule
Rule
,
attr
admission
.
Attributes
)
bool
{
opRes
,
opSub
:=
r
.
Attr
.
GetResource
()
.
Resource
,
r
.
Attr
.
GetSubresource
()
if
len
(
rule
.
ResourceNames
)
==
0
{
for
_
,
res
:=
range
r
.
Rule
.
Resources
{
return
true
res
,
sub
:=
splitResource
(
res
)
resMatch
:=
res
==
"*"
||
res
==
opRes
subMatch
:=
sub
==
"*"
||
sub
==
opSub
if
resMatch
&&
subMatch
{
return
true
}
}
}
return
false
return
indexOf
(
rule
.
ResourceNames
,
attr
.
GetName
())
>
-
1
}
}
plugin/pkg/admission/webhook/rules_test.go
View file @
d6e1140b
This diff is collapsed.
Click to expand it.
plugin/pkg/admission/webhook/types.go
deleted
100644 → 0
View file @
f4403d26
/*
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 checks a webhook for configured operation admission
package
webhook
import
(
"time"
"k8s.io/apiserver/pkg/admission"
)
// FailAction is the action to take whenever a webhook fails and there are no more retries available
type
FailAction
string
const
(
// Allow is the fail action taken whenever the webhook call fails and there are no more retries
Allow
FailAction
=
"ALLOW"
// Deny is the fail action taken whenever the webhook call fails and there are no more retries
Deny
FailAction
=
"DENY"
)
// GenericAdmissionWebhookConfig holds configuration for an admission webhook
type
GenericAdmissionWebhookConfig
struct
{
KubeConfigFile
string
`json:"kubeConfigFile"`
RetryBackoff
time
.
Duration
`json:"retryBackoff"`
Rules
[]
Rule
`json:"rules"`
}
// Rule is the type defining an admission rule in the admission controller configuration file
type
Rule
struct
{
// APIGroups is a list of API groups that contain the resource this rule applies to.
APIGroups
[]
string
`json:"apiGroups"`
// FailAction is the action to take whenever the webhook fails and there are no more retries (Default: DENY)
FailAction
FailAction
`json:"failAction"`
// Namespaces is a list of namespaces this rule applies to.
Namespaces
[]
string
`json:"namespaces"`
// Operations is a list of admission operations this rule applies to.
Operations
[]
admission
.
Operation
`json:"operations"`
// Resources is a list of resources this rule applies to.
Resources
[]
string
`json:"resources"`
// ResourceNames is a list of resource names this rule applies to.
ResourceNames
[]
string
`json:"resourceNames"`
// Type is the admission rule type
Type
RuleType
`json:"type"`
}
// RuleType is the type of admission rule
type
RuleType
string
const
(
// Send is the rule type for when a matching admission.Attributes should be sent to the webhook
Send
RuleType
=
"SEND"
// Skip is the rule type for when a matching admission.Attributes should not be sent to the webhook
Skip
RuleType
=
"SKIP"
)
staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/group_version.go
View file @
d6e1140b
...
@@ -29,8 +29,8 @@ import (
...
@@ -29,8 +29,8 @@ import (
//
//
// +protobuf.options.(gogoproto.goproto_stringer)=false
// +protobuf.options.(gogoproto.goproto_stringer)=false
type
GroupResource
struct
{
type
GroupResource
struct
{
Group
string
`protobuf:"bytes,1,opt,name=group"`
Group
string
`
json:"group"
protobuf:"bytes,1,opt,name=group"`
Resource
string
`protobuf:"bytes,2,opt,name=resource"`
Resource
string
`
json:"resource"
protobuf:"bytes,2,opt,name=resource"`
}
}
func
(
gr
*
GroupResource
)
String
()
string
{
func
(
gr
*
GroupResource
)
String
()
string
{
...
@@ -45,9 +45,9 @@ func (gr *GroupResource) String() string {
...
@@ -45,9 +45,9 @@ func (gr *GroupResource) String() string {
//
//
// +protobuf.options.(gogoproto.goproto_stringer)=false
// +protobuf.options.(gogoproto.goproto_stringer)=false
type
GroupVersionResource
struct
{
type
GroupVersionResource
struct
{
Group
string
`protobuf:"bytes,1,opt,name=group"`
Group
string
`
json:"group"
protobuf:"bytes,1,opt,name=group"`
Version
string
`protobuf:"bytes,2,opt,name=version"`
Version
string
`
json:"version"
protobuf:"bytes,2,opt,name=version"`
Resource
string
`protobuf:"bytes,3,opt,name=resource"`
Resource
string
`
json:"resource"
protobuf:"bytes,3,opt,name=resource"`
}
}
func
(
gvr
*
GroupVersionResource
)
String
()
string
{
func
(
gvr
*
GroupVersionResource
)
String
()
string
{
...
@@ -59,8 +59,8 @@ func (gvr *GroupVersionResource) String() string {
...
@@ -59,8 +59,8 @@ func (gvr *GroupVersionResource) String() string {
//
//
// +protobuf.options.(gogoproto.goproto_stringer)=false
// +protobuf.options.(gogoproto.goproto_stringer)=false
type
GroupKind
struct
{
type
GroupKind
struct
{
Group
string
`protobuf:"bytes,1,opt,name=group"`
Group
string
`
json:"group"
protobuf:"bytes,1,opt,name=group"`
Kind
string
`protobuf:"bytes,2,opt,name=kind"`
Kind
string
`
json:"kind"
protobuf:"bytes,2,opt,name=kind"`
}
}
func
(
gk
*
GroupKind
)
String
()
string
{
func
(
gk
*
GroupKind
)
String
()
string
{
...
@@ -88,8 +88,8 @@ func (gvk GroupVersionKind) String() string {
...
@@ -88,8 +88,8 @@ func (gvk GroupVersionKind) String() string {
//
//
// +protobuf.options.(gogoproto.goproto_stringer)=false
// +protobuf.options.(gogoproto.goproto_stringer)=false
type
GroupVersion
struct
{
type
GroupVersion
struct
{
Group
string
`protobuf:"bytes,1,opt,name=group"`
Group
string
`
json:"group"
protobuf:"bytes,1,opt,name=group"`
Version
string
`protobuf:"bytes,2,opt,name=version"`
Version
string
`
json:"version"
protobuf:"bytes,2,opt,name=version"`
}
}
// Empty returns true if group and version are empty
// Empty returns true if group and version are empty
...
...
staging/src/k8s.io/client-go/rest/request.go
View file @
d6e1140b
...
@@ -1148,6 +1148,9 @@ func (r Result) Into(obj runtime.Object) error {
...
@@ -1148,6 +1148,9 @@ func (r Result) Into(obj runtime.Object) error {
if
r
.
decoder
==
nil
{
if
r
.
decoder
==
nil
{
return
fmt
.
Errorf
(
"serializer for %s doesn't exist"
,
r
.
contentType
)
return
fmt
.
Errorf
(
"serializer for %s doesn't exist"
,
r
.
contentType
)
}
}
if
len
(
r
.
body
)
==
0
{
return
fmt
.
Errorf
(
"0-length response"
)
}
out
,
_
,
err
:=
r
.
decoder
.
Decode
(
r
.
body
,
nil
,
obj
)
out
,
_
,
err
:=
r
.
decoder
.
Decode
(
r
.
body
,
nil
,
obj
)
if
err
!=
nil
||
out
==
obj
{
if
err
!=
nil
||
out
==
obj
{
...
...
staging/src/k8s.io/client-go/rest/request_test.go
View file @
d6e1140b
...
@@ -329,6 +329,16 @@ func TestResultIntoWithErrReturnsErr(t *testing.T) {
...
@@ -329,6 +329,16 @@ func TestResultIntoWithErrReturnsErr(t *testing.T) {
}
}
}
}
func
TestResultIntoWithNoBodyReturnsErr
(
t
*
testing
.
T
)
{
res
:=
Result
{
body
:
[]
byte
{},
decoder
:
scheme
.
Codecs
.
LegacyCodec
(
v1
.
SchemeGroupVersion
),
}
if
err
:=
res
.
Into
(
&
v1
.
Pod
{});
err
==
nil
||
!
strings
.
Contains
(
err
.
Error
(),
"0-length"
)
{
t
.
Errorf
(
"should have complained about 0 length body"
)
}
}
func
TestURLTemplate
(
t
*
testing
.
T
)
{
func
TestURLTemplate
(
t
*
testing
.
T
)
{
uri
,
_
:=
url
.
Parse
(
"http://localhost"
)
uri
,
_
:=
url
.
Parse
(
"http://localhost"
)
r
:=
NewRequest
(
nil
,
"POST"
,
uri
,
""
,
ContentConfig
{
GroupVersion
:
&
schema
.
GroupVersion
{
Group
:
"test"
}},
Serializers
{},
nil
,
nil
)
r
:=
NewRequest
(
nil
,
"POST"
,
uri
,
""
,
ContentConfig
{
GroupVersion
:
&
schema
.
GroupVersion
{
Group
:
"test"
}},
Serializers
{},
nil
,
nil
)
...
...
test/integration/etcd/etcd_storage_path_test.go
View file @
d6e1140b
...
@@ -381,6 +381,10 @@ var ephemeralWhiteList = createEphemeralWhiteList(
...
@@ -381,6 +381,10 @@ var ephemeralWhiteList = createEphemeralWhiteList(
// k8s.io/kubernetes/pkg/apis/policy/v1beta1
// k8s.io/kubernetes/pkg/apis/policy/v1beta1
gvr
(
"policy"
,
"v1beta1"
,
"evictions"
),
// not stored in etcd, deals with evicting kapiv1.Pod
gvr
(
"policy"
,
"v1beta1"
,
"evictions"
),
// not stored in etcd, deals with evicting kapiv1.Pod
// --
// --
// k8s.io/kubernetes/pkg/apis/admission/v1alpha1
gvr
(
"admission.k8s.io"
,
"v1alpha1"
,
"admissionreviews"
),
// not stored in etcd, call out to webhooks.
// --
)
)
// Only add kinds to this list when there is no mapping from GVK to GVR (and thus there is no way to create the object)
// Only add kinds to this list when there is no mapping from GVK to GVR (and thus there is no way to create the object)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment