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
7006d224
Commit
7006d224
authored
Oct 27, 2017
by
Chao Xu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add NamespaceSelector to the api
business logic in webhook plugin and unit test add a e2e test for namespace selector
parent
7c8596a9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
505 additions
and
25 deletions
+505
-25
types.go
pkg/apis/admissionregistration/types.go
+46
-0
defaults.go
pkg/apis/admissionregistration/v1alpha1/defaults.go
+5
-0
validation.go
pkg/apis/admissionregistration/validation/validation.go
+5
-0
types.go
...ng/src/k8s.io/api/admissionregistration/v1alpha1/types.go
+46
-0
admission.go
...8s.io/apiserver/pkg/admission/plugin/webhook/admission.go
+84
-0
admission_test.go
.../apiserver/pkg/admission/plugin/webhook/admission_test.go
+180
-14
webhook.go
test/e2e/apimachinery/webhook.go
+86
-4
Makefile
test/images/webhook/Makefile
+2
-2
main.go
test/images/webhook/main.go
+51
-5
No files found.
pkg/apis/admissionregistration/types.go
View file @
7006d224
...
@@ -191,6 +191,52 @@ type Webhook struct {
...
@@ -191,6 +191,52 @@ type Webhook struct {
// allowed values are Ignore or Fail. Defaults to Ignore.
// allowed values are Ignore or Fail. Defaults to Ignore.
// +optional
// +optional
FailurePolicy
*
FailurePolicyType
FailurePolicy
*
FailurePolicyType
// NamespaceSelector decides whether to run the webhook on an object based
// on whether the namespace for that object matches the selector. If the
// object itself is a namespace, the matching is performed on
// object.metadata.labels. If the object is other cluster scoped resource,
// it is not subjected to the webhook.
//
// For example, to run the webhook on any objects whose namespace is not
// associated with "runlevel" of "0" or "1"; you will set the selector as
// follows:
// "namespaceSelector": {
// "matchExpressions": [
// {
// "key": "runlevel",
// "operator": "NotIn",
// "values": [
// "0",
// "1"
// ]
// }
// ]
// }
//
// If instead you want to only run the webhook on any objects whose
// namespace is associated with the "environment" of "prod" or "staging";
// you will set the selector as follows:
// "namespaceSelector": {
// "matchExpressions": [
// {
// "key": "environment",
// "operator": "In",
// "values": [
// "prod",
// "staging"
// ]
// }
// ]
// }
//
// See
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
// for more examples of label selectors.
//
// Default to the empty LabelSelector, which matches everything.
// +optional
NamespaceSelector
*
metav1
.
LabelSelector
}
}
// RuleWithOperations is a tuple of Operations and Resources. It is recommended to make
// RuleWithOperations is a tuple of Operations and Resources. It is recommended to make
...
...
pkg/apis/admissionregistration/v1alpha1/defaults.go
View file @
7006d224
...
@@ -18,6 +18,7 @@ package v1alpha1
...
@@ -18,6 +18,7 @@ package v1alpha1
import
(
import
(
admissionregistrationv1alpha1
"k8s.io/api/admissionregistration/v1alpha1"
admissionregistrationv1alpha1
"k8s.io/api/admissionregistration/v1alpha1"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime"
)
)
...
@@ -30,4 +31,8 @@ func SetDefaults_Webhook(obj *admissionregistrationv1alpha1.Webhook) {
...
@@ -30,4 +31,8 @@ func SetDefaults_Webhook(obj *admissionregistrationv1alpha1.Webhook) {
policy
:=
admissionregistrationv1alpha1
.
Ignore
policy
:=
admissionregistrationv1alpha1
.
Ignore
obj
.
FailurePolicy
=
&
policy
obj
.
FailurePolicy
=
&
policy
}
}
if
obj
.
NamespaceSelector
==
nil
{
selector
:=
metav1
.
LabelSelector
{}
obj
.
NamespaceSelector
=
&
selector
}
}
}
pkg/apis/admissionregistration/validation/validation.go
View file @
7006d224
...
@@ -21,6 +21,7 @@ import (
...
@@ -21,6 +21,7 @@ import (
"strings"
"strings"
genericvalidation
"k8s.io/apimachinery/pkg/api/validation"
genericvalidation
"k8s.io/apimachinery/pkg/api/validation"
metav1validation
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/validation/field"
...
@@ -195,6 +196,10 @@ func validateWebhook(hook *admissionregistration.Webhook, fldPath *field.Path) f
...
@@ -195,6 +196,10 @@ func validateWebhook(hook *admissionregistration.Webhook, fldPath *field.Path) f
allErrors
=
append
(
allErrors
,
validateURLPath
(
fldPath
.
Child
(
"clientConfig"
,
"urlPath"
),
hook
.
ClientConfig
.
URLPath
)
...
)
allErrors
=
append
(
allErrors
,
validateURLPath
(
fldPath
.
Child
(
"clientConfig"
,
"urlPath"
),
hook
.
ClientConfig
.
URLPath
)
...
)
}
}
if
hook
.
NamespaceSelector
!=
nil
{
allErrors
=
append
(
allErrors
,
metav1validation
.
ValidateLabelSelector
(
hook
.
NamespaceSelector
,
fldPath
.
Child
(
"namespaceSelector"
))
...
)
}
return
allErrors
return
allErrors
}
}
...
...
staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go
View file @
7006d224
...
@@ -197,6 +197,52 @@ type Webhook struct {
...
@@ -197,6 +197,52 @@ type Webhook struct {
// allowed values are Ignore or Fail. Defaults to Ignore.
// allowed values are Ignore or Fail. Defaults to Ignore.
// +optional
// +optional
FailurePolicy
*
FailurePolicyType
`json:"failurePolicy,omitempty" protobuf:"bytes,4,opt,name=failurePolicy,casttype=FailurePolicyType"`
FailurePolicy
*
FailurePolicyType
`json:"failurePolicy,omitempty" protobuf:"bytes,4,opt,name=failurePolicy,casttype=FailurePolicyType"`
// NamespaceSelector decides whether to run the webhook on an object based
// on whether the namespace for that object matches the selector. If the
// object itself is a namespace, the matching is performed on
// object.metadata.labels. If the object is other cluster scoped resource,
// it is not subjected to the webhook.
//
// For example, to run the webhook on any objects whose namespace is not
// associated with "runlevel" of "0" or "1"; you will set the selector as
// follows:
// "namespaceSelector": {
// "matchExpressions": [
// {
// "key": "runlevel",
// "operator": "NotIn",
// "values": [
// "0",
// "1"
// ]
// }
// ]
// }
//
// If instead you want to only run the webhook on any objects whose
// namespace is associated with the "environment" of "prod" or "staging";
// you will set the selector as follows:
// "namespaceSelector": {
// "matchExpressions": [
// {
// "key": "environment",
// "operator": "In",
// "values": [
// "prod",
// "staging"
// ]
// }
// ]
// }
//
// See
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
// for more examples of label selectors.
//
// Default to the empty LabelSelector, which matches everything.
// +optional
NamespaceSelector
*
metav1
.
LabelSelector
`json:"namespaceSelector,omitempty" protobuf:"bytes,5,opt,name=namespaceSelector"`
}
}
// RuleWithOperations is a tuple of Operations and Resources. It is recommended to make
// RuleWithOperations is a tuple of Operations and Resources. It is recommended to make
...
...
staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/admission.go
View file @
7006d224
...
@@ -32,7 +32,9 @@ import (
...
@@ -32,7 +32,9 @@ import (
admissionv1alpha1
"k8s.io/api/admission/v1alpha1"
admissionv1alpha1
"k8s.io/api/admission/v1alpha1"
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/api/admissionregistration/v1alpha1"
apierrors
"k8s.io/apimachinery/pkg/api/errors"
apierrors
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime
"k8s.io/apimachinery/pkg/util/runtime"
utilruntime
"k8s.io/apimachinery/pkg/util/runtime"
...
@@ -41,7 +43,9 @@ import (
...
@@ -41,7 +43,9 @@ import (
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/configuration"
"k8s.io/apiserver/pkg/admission/configuration"
genericadmissioninit
"k8s.io/apiserver/pkg/admission/initializer"
genericadmissioninit
"k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/client-go/informers"
clientset
"k8s.io/client-go/kubernetes"
clientset
"k8s.io/client-go/kubernetes"
corelisters
"k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/rest"
)
)
...
@@ -123,6 +127,8 @@ type GenericAdmissionWebhook struct {
...
@@ -123,6 +127,8 @@ type GenericAdmissionWebhook struct {
hookSource
WebhookSource
hookSource
WebhookSource
serviceResolver
ServiceResolver
serviceResolver
ServiceResolver
negotiatedSerializer
runtime
.
NegotiatedSerializer
negotiatedSerializer
runtime
.
NegotiatedSerializer
namespaceLister
corelisters
.
NamespaceLister
client
clientset
.
Interface
authInfoResolver
AuthenticationInfoResolver
authInfoResolver
AuthenticationInfoResolver
cache
*
lru
.
Cache
cache
*
lru
.
Cache
...
@@ -163,9 +169,17 @@ func (a *GenericAdmissionWebhook) SetScheme(scheme *runtime.Scheme) {
...
@@ -163,9 +169,17 @@ func (a *GenericAdmissionWebhook) SetScheme(scheme *runtime.Scheme) {
// WantsExternalKubeClientSet defines a function which sets external ClientSet for admission plugins that need it
// WantsExternalKubeClientSet defines a function which sets external ClientSet for admission plugins that need it
func
(
a
*
GenericAdmissionWebhook
)
SetExternalKubeClientSet
(
client
clientset
.
Interface
)
{
func
(
a
*
GenericAdmissionWebhook
)
SetExternalKubeClientSet
(
client
clientset
.
Interface
)
{
a
.
client
=
client
a
.
hookSource
=
configuration
.
NewValidatingWebhookConfigurationManager
(
client
.
AdmissionregistrationV1alpha1
()
.
ValidatingWebhookConfigurations
())
a
.
hookSource
=
configuration
.
NewValidatingWebhookConfigurationManager
(
client
.
AdmissionregistrationV1alpha1
()
.
ValidatingWebhookConfigurations
())
}
}
// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
func
(
a
*
GenericAdmissionWebhook
)
SetExternalKubeInformerFactory
(
f
informers
.
SharedInformerFactory
)
{
namespaceInformer
:=
f
.
Core
()
.
V1
()
.
Namespaces
()
a
.
namespaceLister
=
namespaceInformer
.
Lister
()
a
.
SetReadyFunc
(
namespaceInformer
.
Informer
()
.
HasSynced
)
}
// ValidateInitialization implements the InitializationValidator interface.
// ValidateInitialization implements the InitializationValidator interface.
func
(
a
*
GenericAdmissionWebhook
)
ValidateInitialization
()
error
{
func
(
a
*
GenericAdmissionWebhook
)
ValidateInitialization
()
error
{
if
a
.
hookSource
==
nil
{
if
a
.
hookSource
==
nil
{
...
@@ -174,6 +188,9 @@ func (a *GenericAdmissionWebhook) ValidateInitialization() error {
...
@@ -174,6 +188,9 @@ func (a *GenericAdmissionWebhook) ValidateInitialization() error {
if
a
.
negotiatedSerializer
==
nil
{
if
a
.
negotiatedSerializer
==
nil
{
return
fmt
.
Errorf
(
"the GenericAdmissionWebhook admission plugin requires a runtime.Scheme to be provided to derive a serializer"
)
return
fmt
.
Errorf
(
"the GenericAdmissionWebhook admission plugin requires a runtime.Scheme to be provided to derive a serializer"
)
}
}
if
a
.
namespaceLister
==
nil
{
return
fmt
.
Errorf
(
"the GenericAdmissionWebhook admission plugin requires a namespaceLister"
)
}
go
a
.
hookSource
.
Run
(
wait
.
NeverStop
)
go
a
.
hookSource
.
Run
(
wait
.
NeverStop
)
return
nil
return
nil
}
}
...
@@ -255,7 +272,74 @@ func (a *GenericAdmissionWebhook) Admit(attr admission.Attributes) error {
...
@@ -255,7 +272,74 @@ func (a *GenericAdmissionWebhook) Admit(attr admission.Attributes) error {
return
errs
[
0
]
return
errs
[
0
]
}
}
func
(
a
*
GenericAdmissionWebhook
)
getNamespaceLabels
(
attr
admission
.
Attributes
)
(
map
[
string
]
string
,
error
)
{
// If the request itself is creating or updating a namespace, then get the
// labels from attr.Object, because namespaceLister doesn't have the latest
// namespace yet.
//
// However, if the request is deleting a namespace, then get the label from
// the namespace in the namespaceLister, because a delete request is not
// going to change the object, and attr.Object will be a DeleteOptions
// rather than a namespace object.
if
attr
.
GetResource
()
.
Resource
==
"namespaces"
&&
len
(
attr
.
GetSubresource
())
==
0
&&
(
attr
.
GetOperation
()
==
admission
.
Create
||
attr
.
GetOperation
()
==
admission
.
Update
)
{
accessor
,
err
:=
meta
.
Accessor
(
attr
.
GetObject
())
if
err
!=
nil
{
return
nil
,
err
}
return
accessor
.
GetLabels
(),
nil
}
namespaceName
:=
attr
.
GetNamespace
()
namespace
,
err
:=
a
.
namespaceLister
.
Get
(
namespaceName
)
if
err
!=
nil
&&
!
apierrors
.
IsNotFound
(
err
)
{
return
nil
,
err
}
if
apierrors
.
IsNotFound
(
err
)
{
// in case of latency in our caches, make a call direct to storage to verify that it truly exists or not
namespace
,
err
=
a
.
client
.
Core
()
.
Namespaces
()
.
Get
(
namespaceName
,
metav1
.
GetOptions
{})
if
err
!=
nil
{
return
nil
,
err
}
}
return
namespace
.
Labels
,
nil
}
// whether the request is exempted by the webhook because of the
// namespaceSelector of the webhook.
func
(
a
*
GenericAdmissionWebhook
)
exemptedByNamespaceSelector
(
h
*
v1alpha1
.
Webhook
,
attr
admission
.
Attributes
)
(
bool
,
error
)
{
namespaceName
:=
attr
.
GetNamespace
()
if
len
(
namespaceName
)
==
0
&&
attr
.
GetResource
()
.
Resource
!=
"namespaces"
{
// If the request is about a cluster scoped resource, and it is not a
// namespace, it is exempted from all webhooks for now.
// TODO: figure out a way selective exempt cluster scoped resources.
// Also update the comment in types.go
return
true
,
nil
}
namespaceLabels
,
err
:=
a
.
getNamespaceLabels
(
attr
)
if
apierrors
.
IsNotFound
(
err
)
{
return
false
,
err
}
if
err
!=
nil
{
return
false
,
apierrors
.
NewInternalError
(
err
)
}
// TODO: adding an LRU cache to cache the translation
selector
,
err
:=
metav1
.
LabelSelectorAsSelector
(
h
.
NamespaceSelector
)
if
err
!=
nil
{
return
false
,
apierrors
.
NewInternalError
(
err
)
}
return
!
selector
.
Matches
(
labels
.
Set
(
namespaceLabels
)),
nil
}
func
(
a
*
GenericAdmissionWebhook
)
callHook
(
ctx
context
.
Context
,
h
*
v1alpha1
.
Webhook
,
attr
admission
.
Attributes
)
error
{
func
(
a
*
GenericAdmissionWebhook
)
callHook
(
ctx
context
.
Context
,
h
*
v1alpha1
.
Webhook
,
attr
admission
.
Attributes
)
error
{
excluded
,
err
:=
a
.
exemptedByNamespaceSelector
(
h
,
attr
)
if
err
!=
nil
{
return
err
}
if
excluded
{
return
nil
}
matches
:=
false
matches
:=
false
for
_
,
r
:=
range
h
.
Rules
{
for
_
,
r
:=
range
h
.
Rules
{
m
:=
RuleMatcher
{
Rule
:
r
,
Attr
:
attr
}
m
:=
RuleMatcher
{
Rule
:
r
,
Attr
:
attr
}
...
...
staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/admission_test.go
View file @
7006d224
...
@@ -24,16 +24,20 @@ import (
...
@@ -24,16 +24,20 @@ import (
"net/http"
"net/http"
"net/http/httptest"
"net/http/httptest"
"net/url"
"net/url"
"reflect"
"strings"
"strings"
"sync/atomic"
"sync/atomic"
"testing"
"testing"
"k8s.io/api/admission/v1alpha1"
"k8s.io/api/admission/v1alpha1"
registrationv1alpha1
"k8s.io/api/admissionregistration/v1alpha1"
registrationv1alpha1
"k8s.io/api/admissionregistration/v1alpha1"
api
"k8s.io/api/core/v1"
corev1
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
apierrors
"k8s.io/apimachinery/pkg/api/errors"
apierrors
"k8s.io/apimachinery/pkg/api/errors"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/client-go/rest"
"k8s.io/client-go/rest"
...
@@ -48,6 +52,11 @@ func (f *fakeHookSource) Webhooks() (*registrationv1alpha1.ValidatingWebhookConf
...
@@ -48,6 +52,11 @@ func (f *fakeHookSource) Webhooks() (*registrationv1alpha1.ValidatingWebhookConf
if
f
.
err
!=
nil
{
if
f
.
err
!=
nil
{
return
nil
,
f
.
err
return
nil
,
f
.
err
}
}
for
i
,
h
:=
range
f
.
hooks
{
if
h
.
NamespaceSelector
==
nil
{
f
.
hooks
[
i
]
.
NamespaceSelector
=
&
metav1
.
LabelSelector
{}
}
}
return
&
registrationv1alpha1
.
ValidatingWebhookConfiguration
{
Webhooks
:
f
.
hooks
},
nil
return
&
registrationv1alpha1
.
ValidatingWebhookConfiguration
{
Webhooks
:
f
.
hooks
},
nil
}
}
...
@@ -65,11 +74,26 @@ func (f fakeServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL,
...
@@ -65,11 +74,26 @@ func (f fakeServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL,
return
&
u
,
nil
return
&
u
,
nil
}
}
type
fakeNamespaceLister
struct
{
namespaces
map
[
string
]
*
corev1
.
Namespace
}
func
(
f
fakeNamespaceLister
)
List
(
selector
labels
.
Selector
)
(
ret
[]
*
corev1
.
Namespace
,
err
error
)
{
return
nil
,
nil
}
func
(
f
fakeNamespaceLister
)
Get
(
name
string
)
(
*
corev1
.
Namespace
,
error
)
{
ns
,
ok
:=
f
.
namespaces
[
name
]
if
ok
{
return
ns
,
nil
}
return
nil
,
errors
.
NewNotFound
(
corev1
.
Resource
(
"namespaces"
),
name
)
}
// TestAdmit tests that GenericAdmissionWebhook#Admit works as expected
// TestAdmit tests that GenericAdmissionWebhook#Admit works as expected
func
TestAdmit
(
t
*
testing
.
T
)
{
func
TestAdmit
(
t
*
testing
.
T
)
{
scheme
:=
runtime
.
NewScheme
()
scheme
:=
runtime
.
NewScheme
()
v1alpha1
.
AddToScheme
(
scheme
)
v1alpha1
.
AddToScheme
(
scheme
)
api
.
AddToScheme
(
scheme
)
corev1
.
AddToScheme
(
scheme
)
testServer
:=
newTestServer
(
t
)
testServer
:=
newTestServer
(
t
)
testServer
.
StartTLS
()
testServer
.
StartTLS
()
...
@@ -85,12 +109,22 @@ func TestAdmit(t *testing.T) {
...
@@ -85,12 +109,22 @@ func TestAdmit(t *testing.T) {
wh
.
authInfoResolver
=
newFakeAuthenticationInfoResolver
()
wh
.
authInfoResolver
=
newFakeAuthenticationInfoResolver
()
wh
.
serviceResolver
=
fakeServiceResolver
{
base
:
*
serverURL
}
wh
.
serviceResolver
=
fakeServiceResolver
{
base
:
*
serverURL
}
wh
.
SetScheme
(
scheme
)
wh
.
SetScheme
(
scheme
)
namespace
:=
"webhook-test"
wh
.
namespaceLister
=
fakeNamespaceLister
{
map
[
string
]
*
corev1
.
Namespace
{
namespace
:
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Labels
:
map
[
string
]
string
{
"runlevel"
:
"0"
,
},
},
},
},
}
// Set up a test object for the call
// Set up a test object for the call
kind
:=
api
.
SchemeGroupVersion
.
WithKind
(
"Pod"
)
kind
:=
corev1
.
SchemeGroupVersion
.
WithKind
(
"Pod"
)
name
:=
"my-pod"
name
:=
"my-pod"
namespace
:=
"webhook-test"
object
:=
corev1
.
Pod
{
object
:=
api
.
Pod
{
ObjectMeta
:
metav1
.
ObjectMeta
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Labels
:
map
[
string
]
string
{
Labels
:
map
[
string
]
string
{
"pod.name"
:
name
,
"pod.name"
:
name
,
...
@@ -103,11 +137,11 @@ func TestAdmit(t *testing.T) {
...
@@ -103,11 +137,11 @@ func TestAdmit(t *testing.T) {
Kind
:
"Pod"
,
Kind
:
"Pod"
,
},
},
}
}
oldObject
:=
api
.
Pod
{
oldObject
:=
corev1
.
Pod
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
name
,
Namespace
:
namespace
},
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
name
,
Namespace
:
namespace
},
}
}
operation
:=
admission
.
Update
operation
:=
admission
.
Update
resource
:=
api
.
Resource
(
"pods"
)
.
WithVersion
(
"v1"
)
resource
:=
corev1
.
Resource
(
"pods"
)
.
WithVersion
(
"v1"
)
subResource
:=
""
subResource
:=
""
userInfo
:=
user
.
DefaultInfo
{
userInfo
:=
user
.
DefaultInfo
{
Name
:
"webhook-test"
,
Name
:
"webhook-test"
,
...
@@ -167,6 +201,40 @@ func TestAdmit(t *testing.T) {
...
@@ -167,6 +201,40 @@ func TestAdmit(t *testing.T) {
},
},
errorContains
:
"you shall not pass"
,
errorContains
:
"you shall not pass"
,
},
},
"match & disallow & but allowed because namespaceSelector exempt the namespace"
:
{
hookSource
:
fakeHookSource
{
hooks
:
[]
registrationv1alpha1
.
Webhook
{{
Name
:
"disallow"
,
ClientConfig
:
newFakeHookClientConfig
(
"disallow"
),
Rules
:
newMatchEverythingRules
(),
NamespaceSelector
:
&
metav1
.
LabelSelector
{
MatchExpressions
:
[]
metav1
.
LabelSelectorRequirement
{{
Key
:
"runlevel"
,
Values
:
[]
string
{
"1"
},
Operator
:
metav1
.
LabelSelectorOpIn
,
}},
},
}},
},
expectAllow
:
true
,
},
"match & disallow & but allowed because namespaceSelector exempt the namespace ii"
:
{
hookSource
:
fakeHookSource
{
hooks
:
[]
registrationv1alpha1
.
Webhook
{{
Name
:
"disallow"
,
ClientConfig
:
newFakeHookClientConfig
(
"disallow"
),
Rules
:
newMatchEverythingRules
(),
NamespaceSelector
:
&
metav1
.
LabelSelector
{
MatchExpressions
:
[]
metav1
.
LabelSelectorRequirement
{{
Key
:
"runlevel"
,
Values
:
[]
string
{
"0"
},
Operator
:
metav1
.
LabelSelectorOpNotIn
,
}},
},
}},
},
expectAllow
:
true
,
},
"match & fail (but allow because fail open)"
:
{
"match & fail (but allow because fail open)"
:
{
hookSource
:
fakeHookSource
{
hookSource
:
fakeHookSource
{
hooks
:
[]
registrationv1alpha1
.
Webhook
{{
hooks
:
[]
registrationv1alpha1
.
Webhook
{{
...
@@ -230,9 +298,11 @@ func TestAdmit(t *testing.T) {
...
@@ -230,9 +298,11 @@ func TestAdmit(t *testing.T) {
}
}
for
name
,
tt
:=
range
table
{
for
name
,
tt
:=
range
table
{
if
!
strings
.
Contains
(
name
,
"no match"
)
{
continue
}
t
.
Run
(
name
,
func
(
t
*
testing
.
T
)
{
t
.
Run
(
name
,
func
(
t
*
testing
.
T
)
{
wh
.
hookSource
=
&
tt
.
hookSource
wh
.
hookSource
=
&
tt
.
hookSource
err
=
wh
.
Admit
(
admission
.
NewAttributesRecord
(
&
object
,
&
oldObject
,
kind
,
namespace
,
name
,
resource
,
subResource
,
operation
,
&
userInfo
))
err
=
wh
.
Admit
(
admission
.
NewAttributesRecord
(
&
object
,
&
oldObject
,
kind
,
namespace
,
name
,
resource
,
subResource
,
operation
,
&
userInfo
))
if
tt
.
expectAllow
!=
(
err
==
nil
)
{
if
tt
.
expectAllow
!=
(
err
==
nil
)
{
t
.
Errorf
(
"expected allowed=%v, but got err=%v"
,
tt
.
expectAllow
,
err
)
t
.
Errorf
(
"expected allowed=%v, but got err=%v"
,
tt
.
expectAllow
,
err
)
...
@@ -254,7 +324,7 @@ func TestAdmit(t *testing.T) {
...
@@ -254,7 +324,7 @@ func TestAdmit(t *testing.T) {
func
TestAdmitCachedClient
(
t
*
testing
.
T
)
{
func
TestAdmitCachedClient
(
t
*
testing
.
T
)
{
scheme
:=
runtime
.
NewScheme
()
scheme
:=
runtime
.
NewScheme
()
v1alpha1
.
AddToScheme
(
scheme
)
v1alpha1
.
AddToScheme
(
scheme
)
api
.
AddToScheme
(
scheme
)
corev1
.
AddToScheme
(
scheme
)
testServer
:=
newTestServer
(
t
)
testServer
:=
newTestServer
(
t
)
testServer
.
StartTLS
()
testServer
.
StartTLS
()
...
@@ -270,12 +340,22 @@ func TestAdmitCachedClient(t *testing.T) {
...
@@ -270,12 +340,22 @@ func TestAdmitCachedClient(t *testing.T) {
wh
.
authInfoResolver
=
newFakeAuthenticationInfoResolver
()
wh
.
authInfoResolver
=
newFakeAuthenticationInfoResolver
()
wh
.
serviceResolver
=
fakeServiceResolver
{
base
:
*
serverURL
}
wh
.
serviceResolver
=
fakeServiceResolver
{
base
:
*
serverURL
}
wh
.
SetScheme
(
scheme
)
wh
.
SetScheme
(
scheme
)
namespace
:=
"webhook-test"
wh
.
namespaceLister
=
fakeNamespaceLister
{
map
[
string
]
*
corev1
.
Namespace
{
namespace
:
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Labels
:
map
[
string
]
string
{
"runlevel"
:
"0"
,
},
},
},
},
}
// Set up a test object for the call
// Set up a test object for the call
kind
:=
api
.
SchemeGroupVersion
.
WithKind
(
"Pod"
)
kind
:=
corev1
.
SchemeGroupVersion
.
WithKind
(
"Pod"
)
name
:=
"my-pod"
name
:=
"my-pod"
namespace
:=
"webhook-test"
object
:=
corev1
.
Pod
{
object
:=
api
.
Pod
{
ObjectMeta
:
metav1
.
ObjectMeta
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Labels
:
map
[
string
]
string
{
Labels
:
map
[
string
]
string
{
"pod.name"
:
name
,
"pod.name"
:
name
,
...
@@ -288,11 +368,11 @@ func TestAdmitCachedClient(t *testing.T) {
...
@@ -288,11 +368,11 @@ func TestAdmitCachedClient(t *testing.T) {
Kind
:
"Pod"
,
Kind
:
"Pod"
,
},
},
}
}
oldObject
:=
api
.
Pod
{
oldObject
:=
corev1
.
Pod
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
name
,
Namespace
:
namespace
},
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
name
,
Namespace
:
namespace
},
}
}
operation
:=
admission
.
Update
operation
:=
admission
.
Update
resource
:=
api
.
Resource
(
"pods"
)
.
WithVersion
(
"v1"
)
resource
:=
corev1
.
Resource
(
"pods"
)
.
WithVersion
(
"v1"
)
subResource
:=
""
subResource
:=
""
userInfo
:=
user
.
DefaultInfo
{
userInfo
:=
user
.
DefaultInfo
{
Name
:
"webhook-test"
,
Name
:
"webhook-test"
,
...
@@ -522,3 +602,89 @@ func newMatchEverythingRules() []registrationv1alpha1.RuleWithOperations {
...
@@ -522,3 +602,89 @@ func newMatchEverythingRules() []registrationv1alpha1.RuleWithOperations {
},
},
}}
}}
}
}
func
TestGetNamespaceLabels
(
t
*
testing
.
T
)
{
namespace1Labels
:=
map
[
string
]
string
{
"runlevel"
:
"1"
,
}
namespace1
:=
corev1
.
Namespace
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"1"
,
Labels
:
namespace1Labels
,
},
}
namespace2Labels
:=
map
[
string
]
string
{
"runlevel"
:
"2"
,
}
namespace2
:=
corev1
.
Namespace
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"2"
,
Labels
:
namespace2Labels
,
},
}
namespaceLister
:=
fakeNamespaceLister
{
map
[
string
]
*
corev1
.
Namespace
{
"1"
:
&
namespace1
,
},
}
tests
:=
[]
struct
{
name
string
attr
admission
.
Attributes
expectedLabels
map
[
string
]
string
}{
{
name
:
"request is for creating namespace, the labels should be from the object itself"
,
attr
:
admission
.
NewAttributesRecord
(
&
namespace2
,
nil
,
schema
.
GroupVersionKind
{},
""
,
namespace2
.
Name
,
schema
.
GroupVersionResource
{
Resource
:
"namespaces"
},
""
,
admission
.
Create
,
nil
),
expectedLabels
:
namespace2Labels
,
},
{
name
:
"request is for updating namespace, the labels should be from the new object"
,
attr
:
admission
.
NewAttributesRecord
(
&
namespace2
,
nil
,
schema
.
GroupVersionKind
{},
namespace2
.
Name
,
namespace2
.
Name
,
schema
.
GroupVersionResource
{
Resource
:
"namespaces"
},
""
,
admission
.
Update
,
nil
),
expectedLabels
:
namespace2Labels
,
},
{
name
:
"request is for deleting namespace, the labels should be from the cache"
,
attr
:
admission
.
NewAttributesRecord
(
&
namespace2
,
nil
,
schema
.
GroupVersionKind
{},
namespace1
.
Name
,
namespace1
.
Name
,
schema
.
GroupVersionResource
{
Resource
:
"namespaces"
},
""
,
admission
.
Delete
,
nil
),
expectedLabels
:
namespace1Labels
,
},
{
name
:
"request is for namespace/finalizer"
,
attr
:
admission
.
NewAttributesRecord
(
nil
,
nil
,
schema
.
GroupVersionKind
{},
namespace1
.
Name
,
"mock-name"
,
schema
.
GroupVersionResource
{
Resource
:
"namespaces"
},
"finalizers"
,
admission
.
Create
,
nil
),
expectedLabels
:
namespace1Labels
,
},
{
name
:
"request is for pod"
,
attr
:
admission
.
NewAttributesRecord
(
nil
,
nil
,
schema
.
GroupVersionKind
{},
namespace1
.
Name
,
"mock-name"
,
schema
.
GroupVersionResource
{
Resource
:
"pods"
},
""
,
admission
.
Create
,
nil
),
expectedLabels
:
namespace1Labels
,
},
}
wh
,
err
:=
NewGenericAdmissionWebhook
(
nil
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
wh
.
namespaceLister
=
namespaceLister
for
_
,
tt
:=
range
tests
{
actualLabels
,
err
:=
wh
.
getNamespaceLabels
(
tt
.
attr
)
if
err
!=
nil
{
t
.
Error
(
err
)
}
if
!
reflect
.
DeepEqual
(
actualLabels
,
tt
.
expectedLabels
)
{
t
.
Errorf
(
"expected labels to be %#v, got %#v"
,
tt
.
expectedLabels
,
actualLabels
)
}
}
}
func
TestExemptClusterScopedResource
(
t
*
testing
.
T
)
{
hook
:=
&
registrationv1alpha1
.
Webhook
{
NamespaceSelector
:
&
metav1
.
LabelSelector
{},
}
attr
:=
admission
.
NewAttributesRecord
(
nil
,
nil
,
schema
.
GroupVersionKind
{},
""
,
"mock-name"
,
schema
.
GroupVersionResource
{
Version
:
"v1"
,
Resource
:
"nodes"
},
""
,
admission
.
Create
,
nil
)
g
:=
GenericAdmissionWebhook
{}
exempted
,
err
:=
g
.
exemptedByNamespaceSelector
(
hook
,
attr
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
!
exempted
{
t
.
Errorf
(
"cluster scoped resources (but not a namespace) should be exempted from all webhooks"
)
}
}
test/e2e/apimachinery/webhook.go
View file @
7006d224
...
@@ -27,6 +27,7 @@ import (
...
@@ -27,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/errors"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/wait"
utilversion
"k8s.io/kubernetes/pkg/util/version"
utilversion
"k8s.io/kubernetes/pkg/util/version"
"k8s.io/kubernetes/test/e2e/framework"
"k8s.io/kubernetes/test/e2e/framework"
...
@@ -41,6 +42,11 @@ const (
...
@@ -41,6 +42,11 @@ const (
serviceName
=
"e2e-test-webhook"
serviceName
=
"e2e-test-webhook"
roleBindingName
=
"webhook-auth-reader"
roleBindingName
=
"webhook-auth-reader"
webhookConfigName
=
"e2e-test-webhook-config"
webhookConfigName
=
"e2e-test-webhook-config"
skipNamespaceLabelKey
=
"skip-webhook-admission"
skipNamespaceLabelValue
=
"yes"
skippedNamespaceName
=
"exempted-namesapce"
disallowedPodName
=
"disallowed-pod"
disallowedConfigMapName
=
"disallowed-configmap"
)
)
var
serverWebhookVersion
=
utilversion
.
MustParseSemantic
(
"v1.8.0"
)
var
serverWebhookVersion
=
utilversion
.
MustParseSemantic
(
"v1.8.0"
)
...
@@ -51,7 +57,7 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
...
@@ -51,7 +57,7 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
cleanWebhookTest
(
f
)
cleanWebhookTest
(
f
)
})
})
It
(
"Should be able to deny pod creation"
,
func
()
{
It
(
"Should be able to deny pod
and configmap
creation"
,
func
()
{
// Make sure the relevant provider supports admission webhook
// Make sure the relevant provider supports admission webhook
framework
.
SkipUnlessServerVersionGTE
(
serverWebhookVersion
,
f
.
ClientSet
.
Discovery
())
framework
.
SkipUnlessServerVersionGTE
(
serverWebhookVersion
,
f
.
ClientSet
.
Discovery
())
framework
.
SkipUnlessProviderIs
(
"gce"
,
"gke"
)
framework
.
SkipUnlessProviderIs
(
"gce"
,
"gke"
)
...
@@ -68,7 +74,7 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
...
@@ -68,7 +74,7 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
// Note that in 1.9 we will have backwards incompatible change to
// Note that in 1.9 we will have backwards incompatible change to
// admission webhooks, so the image will be updated to 1.9 sometime in
// admission webhooks, so the image will be updated to 1.9 sometime in
// the development 1.9 cycle.
// the development 1.9 cycle.
deployWebhookAndService
(
f
,
"gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v
1
"
,
context
)
deployWebhookAndService
(
f
,
"gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v
2
"
,
context
)
registerWebhook
(
f
,
context
)
registerWebhook
(
f
,
context
)
testWebhook
(
f
)
testWebhook
(
f
)
})
})
...
@@ -223,7 +229,7 @@ func registerWebhook(f *framework.Framework, context *certContext) {
...
@@ -223,7 +229,7 @@ func registerWebhook(f *framework.Framework, context *certContext) {
},
},
Webhooks
:
[]
v1alpha1
.
Webhook
{
Webhooks
:
[]
v1alpha1
.
Webhook
{
{
{
Name
:
"
e2e-test-webhook
.k8s.io"
,
Name
:
"
deny-unwanted-pod-container-name-and-label
.k8s.io"
,
Rules
:
[]
v1alpha1
.
RuleWithOperations
{{
Rules
:
[]
v1alpha1
.
RuleWithOperations
{{
Operations
:
[]
v1alpha1
.
OperationType
{
v1alpha1
.
Create
},
Operations
:
[]
v1alpha1
.
OperationType
{
v1alpha1
.
Create
},
Rule
:
v1alpha1
.
Rule
{
Rule
:
v1alpha1
.
Rule
{
...
@@ -237,6 +243,36 @@ func registerWebhook(f *framework.Framework, context *certContext) {
...
@@ -237,6 +243,36 @@ func registerWebhook(f *framework.Framework, context *certContext) {
Namespace
:
namespace
,
Namespace
:
namespace
,
Name
:
serviceName
,
Name
:
serviceName
,
},
},
URLPath
:
"/pods"
,
CABundle
:
context
.
signingCert
,
},
},
{
Name
:
"deny-unwanted-configmap-data.k8s.io"
,
Rules
:
[]
v1alpha1
.
RuleWithOperations
{{
Operations
:
[]
v1alpha1
.
OperationType
{
v1alpha1
.
Create
},
Rule
:
v1alpha1
.
Rule
{
APIGroups
:
[]
string
{
""
},
APIVersions
:
[]
string
{
"v1"
},
Resources
:
[]
string
{
"configmaps"
},
},
}},
// The webhook skips the namespace that has label "skip-webhook-admission":"yes"
NamespaceSelector
:
&
metav1
.
LabelSelector
{
MatchExpressions
:
[]
metav1
.
LabelSelectorRequirement
{
{
Key
:
skipNamespaceLabelKey
,
Operator
:
metav1
.
LabelSelectorOpNotIn
,
Values
:
[]
string
{
skipNamespaceLabelValue
},
},
},
},
ClientConfig
:
v1alpha1
.
WebhookClientConfig
{
Service
:
v1alpha1
.
ServiceReference
{
Namespace
:
namespace
,
Name
:
serviceName
,
},
URLPath
:
"/configmaps"
,
CABundle
:
context
.
signingCert
,
CABundle
:
context
.
signingCert
,
},
},
},
},
...
@@ -262,12 +298,45 @@ func testWebhook(f *framework.Framework) {
...
@@ -262,12 +298,45 @@ func testWebhook(f *framework.Framework) {
// TODO: Test if webhook can detect pod with non-compliant metadata.
// TODO: Test if webhook can detect pod with non-compliant metadata.
// Currently metadata is lost because webhook uses the external version of
// Currently metadata is lost because webhook uses the external version of
// the objects, and the apiserver sends the internal objects.
// the objects, and the apiserver sends the internal objects.
By
(
"create a configmap that should be denied by the webhook"
)
// Creating the configmap, the request should be rejected
configmap
:=
nonCompliantConfigMap
(
f
)
_
,
err
=
client
.
CoreV1
()
.
ConfigMaps
(
f
.
Namespace
.
Name
)
.
Create
(
configmap
)
Expect
(
err
)
.
NotTo
(
BeNil
())
expectedErrMsg
=
"the configmap contains unwanted key and value"
if
!
strings
.
Contains
(
err
.
Error
(),
expectedErrMsg
)
{
framework
.
Failf
(
"expect error contains %q, got %q"
,
expectedErrMsg
,
err
.
Error
())
}
By
(
"create a namespace that bypass the webhook"
)
err
=
wait
.
Poll
(
100
*
time
.
Millisecond
,
30
*
time
.
Second
,
func
()
(
bool
,
error
)
{
_
,
err2
:=
client
.
CoreV1
()
.
Namespaces
()
.
Create
(
&
v1
.
Namespace
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
skippedNamespaceName
,
Labels
:
map
[
string
]
string
{
skipNamespaceLabelKey
:
skipNamespaceLabelValue
,
},
}})
if
err2
!=
nil
{
if
strings
.
HasPrefix
(
err2
.
Error
(),
"object is being deleted:"
)
{
return
false
,
nil
}
return
false
,
err2
}
return
true
,
nil
})
framework
.
ExpectNoError
(
err
,
"creating namespace %q"
,
skippedNamespaceName
)
By
(
"create a configmap that violates the webhook policy but is in a whitelisted namespace"
)
configmap
=
nonCompliantConfigMap
(
f
)
_
,
err
=
client
.
CoreV1
()
.
ConfigMaps
(
skippedNamespaceName
)
.
Create
(
configmap
)
Expect
(
err
)
.
To
(
BeNil
())
}
}
func
nonCompliantPod
(
f
*
framework
.
Framework
)
*
v1
.
Pod
{
func
nonCompliantPod
(
f
*
framework
.
Framework
)
*
v1
.
Pod
{
return
&
v1
.
Pod
{
return
&
v1
.
Pod
{
ObjectMeta
:
metav1
.
ObjectMeta
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"disallowed-pod"
,
Name
:
disallowedPodName
,
Labels
:
map
[
string
]
string
{
Labels
:
map
[
string
]
string
{
"webhook-e2e-test"
:
"disallow"
,
"webhook-e2e-test"
:
"disallow"
,
},
},
...
@@ -283,6 +352,17 @@ func nonCompliantPod(f *framework.Framework) *v1.Pod {
...
@@ -283,6 +352,17 @@ func nonCompliantPod(f *framework.Framework) *v1.Pod {
}
}
}
}
func
nonCompliantConfigMap
(
f
*
framework
.
Framework
)
*
v1
.
ConfigMap
{
return
&
v1
.
ConfigMap
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
disallowedConfigMapName
,
},
Data
:
map
[
string
]
string
{
"webhook-e2e-test"
:
"webhook-disallow"
,
},
}
}
func
cleanWebhookTest
(
f
*
framework
.
Framework
)
{
func
cleanWebhookTest
(
f
*
framework
.
Framework
)
{
client
:=
f
.
ClientSet
client
:=
f
.
ClientSet
_
=
client
.
AdmissionregistrationV1alpha1
()
.
ValidatingWebhookConfigurations
()
.
Delete
(
webhookConfigName
,
nil
)
_
=
client
.
AdmissionregistrationV1alpha1
()
.
ValidatingWebhookConfigurations
()
.
Delete
(
webhookConfigName
,
nil
)
...
@@ -291,4 +371,6 @@ func cleanWebhookTest(f *framework.Framework) {
...
@@ -291,4 +371,6 @@ func cleanWebhookTest(f *framework.Framework) {
_
=
client
.
ExtensionsV1beta1
()
.
Deployments
(
namespaceName
)
.
Delete
(
deploymentName
,
nil
)
_
=
client
.
ExtensionsV1beta1
()
.
Deployments
(
namespaceName
)
.
Delete
(
deploymentName
,
nil
)
_
=
client
.
CoreV1
()
.
Secrets
(
namespaceName
)
.
Delete
(
secretName
,
nil
)
_
=
client
.
CoreV1
()
.
Secrets
(
namespaceName
)
.
Delete
(
secretName
,
nil
)
_
=
client
.
RbacV1beta1
()
.
RoleBindings
(
"kube-system"
)
.
Delete
(
roleBindingName
,
nil
)
_
=
client
.
RbacV1beta1
()
.
RoleBindings
(
"kube-system"
)
.
Delete
(
roleBindingName
,
nil
)
_
=
client
.
CoreV1
()
.
ConfigMaps
(
skippedNamespaceName
)
.
Delete
(
disallowedConfigMapName
,
nil
)
_
=
client
.
CoreV1
()
.
Namespaces
()
.
Delete
(
skippedNamespaceName
,
nil
)
}
}
test/images/webhook/Makefile
View file @
7006d224
...
@@ -14,6 +14,6 @@
...
@@ -14,6 +14,6 @@
build
:
build
:
CGO_ENABLED
=
0
GOOS
=
linux go build
-a
-installsuffix
cgo
-o
webhook .
CGO_ENABLED
=
0
GOOS
=
linux go build
-a
-installsuffix
cgo
-o
webhook .
docker build
--no-cache
-t
gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v
1
.
docker build
--no-cache
-t
gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v
2
.
push
:
push
:
gcloud docker
--
push
gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v1 .
gcloud docker
--
push gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v2
test/images/webhook/main.go
View file @
7006d224
...
@@ -44,7 +44,8 @@ func (c *Config) addFlags() {
...
@@ -44,7 +44,8 @@ func (c *Config) addFlags() {
}
}
// only allow pods to pull images from specific registry.
// only allow pods to pull images from specific registry.
func
admit
(
data
[]
byte
)
*
v1alpha1
.
AdmissionReviewStatus
{
func
admitPods
(
data
[]
byte
)
*
v1alpha1
.
AdmissionReviewStatus
{
glog
.
V
(
2
)
.
Info
(
"admitting pods"
)
ar
:=
v1alpha1
.
AdmissionReview
{}
ar
:=
v1alpha1
.
AdmissionReview
{}
if
err
:=
json
.
Unmarshal
(
data
,
&
ar
);
err
!=
nil
{
if
err
:=
json
.
Unmarshal
(
data
,
&
ar
);
err
!=
nil
{
glog
.
Error
(
err
)
glog
.
Error
(
err
)
...
@@ -86,7 +87,42 @@ func admit(data []byte) *v1alpha1.AdmissionReviewStatus {
...
@@ -86,7 +87,42 @@ func admit(data []byte) *v1alpha1.AdmissionReviewStatus {
return
&
reviewStatus
return
&
reviewStatus
}
}
func
serve
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
// deny configmaps with specific key-value pair.
func
admitConfigMaps
(
data
[]
byte
)
*
v1alpha1
.
AdmissionReviewStatus
{
glog
.
V
(
2
)
.
Info
(
"admitting configmaps"
)
ar
:=
v1alpha1
.
AdmissionReview
{}
if
err
:=
json
.
Unmarshal
(
data
,
&
ar
);
err
!=
nil
{
glog
.
Error
(
err
)
return
nil
}
configMapResource
:=
metav1
.
GroupVersionResource
{
Group
:
""
,
Version
:
"v1"
,
Resource
:
"configmaps"
}
if
ar
.
Spec
.
Resource
!=
configMapResource
{
glog
.
Errorf
(
"expect resource to be %s"
,
configMapResource
)
return
nil
}
raw
:=
ar
.
Spec
.
Object
.
Raw
configmap
:=
v1
.
ConfigMap
{}
if
err
:=
json
.
Unmarshal
(
raw
,
&
configmap
);
err
!=
nil
{
glog
.
Error
(
err
)
return
nil
}
reviewStatus
:=
v1alpha1
.
AdmissionReviewStatus
{}
reviewStatus
.
Allowed
=
true
for
k
,
v
:=
range
configmap
.
Data
{
if
k
==
"webhook-e2e-test"
&&
v
==
"webhook-disallow"
{
reviewStatus
.
Allowed
=
false
reviewStatus
.
Result
=
&
metav1
.
Status
{
Reason
:
"the configmap contains unwanted key and value"
,
}
}
}
return
&
reviewStatus
}
type
admitFunc
func
(
data
[]
byte
)
*
v1alpha1
.
AdmissionReviewStatus
func
serve
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
,
admit
admitFunc
)
{
var
body
[]
byte
var
body
[]
byte
if
r
.
Body
!=
nil
{
if
r
.
Body
!=
nil
{
if
data
,
err
:=
ioutil
.
ReadAll
(
r
.
Body
);
err
==
nil
{
if
data
,
err
:=
ioutil
.
ReadAll
(
r
.
Body
);
err
==
nil
{
...
@@ -102,8 +138,10 @@ func serve(w http.ResponseWriter, r *http.Request) {
...
@@ -102,8 +138,10 @@ func serve(w http.ResponseWriter, r *http.Request) {
}
}
reviewStatus
:=
admit
(
body
)
reviewStatus
:=
admit
(
body
)
ar
:=
v1alpha1
.
AdmissionReview
{
Status
:
*
reviewStatus
,
ar
:=
v1alpha1
.
AdmissionReview
{}
if
reviewStatus
!=
nil
{
ar
.
Status
=
*
reviewStatus
}
}
resp
,
err
:=
json
.
Marshal
(
ar
)
resp
,
err
:=
json
.
Marshal
(
ar
)
...
@@ -115,12 +153,20 @@ func serve(w http.ResponseWriter, r *http.Request) {
...
@@ -115,12 +153,20 @@ func serve(w http.ResponseWriter, r *http.Request) {
}
}
}
}
func
servePods
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
serve
(
w
,
r
,
admitPods
)
}
func
serveConfigmaps
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
serve
(
w
,
r
,
admitConfigMaps
)
}
func
main
()
{
func
main
()
{
var
config
Config
var
config
Config
config
.
addFlags
()
config
.
addFlags
()
flag
.
Parse
()
flag
.
Parse
()
http
.
HandleFunc
(
"/"
,
serve
)
http
.
HandleFunc
(
"/pods"
,
servePods
)
http
.
HandleFunc
(
"/configmaps"
,
serveConfigmaps
)
clientset
:=
getClient
()
clientset
:=
getClient
()
server
:=
&
http
.
Server
{
server
:=
&
http
.
Server
{
Addr
:
":443"
,
Addr
:
":443"
,
...
...
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