Merge pull request #62 from digitalocean/awg/update-webhook-check
Update the DOKS admission controller webhook checkimage-warning-sha256 v0.1.1
commit
56e3306cf7
30
checks.md
30
checks.md
|
@ -209,7 +209,10 @@ spec:
|
||||||
- Name: `admission-controller-webhook`
|
- Name: `admission-controller-webhook`
|
||||||
- Groups: `doks`
|
- Groups: `doks`
|
||||||
|
|
||||||
When you configure admission controllers with webhooks that have a `failurePolicy` set to `Fail`, it prevents managed components like Cilium, kube-proxy, and CoreDNS from starting on a new node during an upgrade. This will result in the cluster upgrade failing.
|
Admission control webhooks can disrupt upgrade and node replacement operations by preventing system components from starting. Specifically, this happens when an admission control webhook:
|
||||||
|
* has failurePolicy set to Fail,
|
||||||
|
* targets a service other than the Kubernetes apiserver, and
|
||||||
|
* applies to both kube-system and the namespace of the targeted service.
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
|
@ -233,7 +236,7 @@ webhooks:
|
||||||
scope: "Namespaced"
|
scope: "Namespaced"
|
||||||
clientConfig:
|
clientConfig:
|
||||||
service:
|
service:
|
||||||
namespace: kube-system
|
namespace: webhook
|
||||||
name: webhook-server
|
name: webhook-server
|
||||||
path: /pods
|
path: /pods
|
||||||
admissionReviewVersions:
|
admissionReviewVersions:
|
||||||
|
@ -244,8 +247,23 @@ webhooks:
|
||||||
|
|
||||||
### How to Fix
|
### How to Fix
|
||||||
|
|
||||||
|
There are a few options:
|
||||||
|
1. Use the `Ignore` `failurePolicy`.
|
||||||
|
2. Use an apiserver extension as your webhook service.
|
||||||
|
3. Explicitly exclude the kube-system namespace.
|
||||||
|
4. Explicitly exclude the webhook service's namespace.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# Recommended: Exclude objects in the `kube-system` namespace by explicitly specifying a namespaceSelector or objectSelector
|
# Recommended: Exclude objects in the `webhook` namespace by explicitly specifying a namespaceSelector.
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: webhook
|
||||||
|
labels:
|
||||||
|
skip-webhooks: "yes"
|
||||||
|
|
||||||
|
---
|
||||||
apiVersion: admissionregistration.k8s.io/v1beta1
|
apiVersion: admissionregistration.k8s.io/v1beta1
|
||||||
kind: ValidatingWebhookConfiguration
|
kind: ValidatingWebhookConfiguration
|
||||||
metadata:
|
metadata:
|
||||||
|
@ -264,7 +282,7 @@ webhooks:
|
||||||
scope: "Namespaced"
|
scope: "Namespaced"
|
||||||
clientConfig:
|
clientConfig:
|
||||||
service:
|
service:
|
||||||
namespace: kube-system
|
namespace: webhook
|
||||||
name: webhook-server
|
name: webhook-server
|
||||||
path: /pods
|
path: /pods
|
||||||
admissionReviewVersions:
|
admissionReviewVersions:
|
||||||
|
@ -273,8 +291,8 @@ webhooks:
|
||||||
failurePolicy: Fail
|
failurePolicy: Fail
|
||||||
namespaceSelector:
|
namespaceSelector:
|
||||||
matchExpressions:
|
matchExpressions:
|
||||||
- key: "doks.digitalocean.com/webhook"
|
- key: "skip-webhooks"
|
||||||
operator: "Exists"
|
operator: "DoesNotExist"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Pod State
|
## Pod State
|
||||||
|
|
|
@ -17,10 +17,13 @@ limitations under the License.
|
||||||
package doks
|
package doks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/digitalocean/clusterlint/checks"
|
"github.com/digitalocean/clusterlint/checks"
|
||||||
"github.com/digitalocean/clusterlint/kube"
|
"github.com/digitalocean/clusterlint/kube"
|
||||||
ar "k8s.io/api/admissionregistration/v1beta1"
|
ar "k8s.io/api/admissionregistration/v1beta1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,46 +46,112 @@ func (w *webhookCheck) Groups() []string {
|
||||||
// Description returns a detailed human-readable description of what this check
|
// Description returns a detailed human-readable description of what this check
|
||||||
// does.
|
// does.
|
||||||
func (w *webhookCheck) Description() string {
|
func (w *webhookCheck) Description() string {
|
||||||
return "Check for admission controllers that could prevent managed components from starting"
|
return "Check for admission control webhooks that could cause problems during upgrades"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run runs this check on a set of Kubernetes objects. It can return warnings
|
// Run runs this check on a set of Kubernetes objects.
|
||||||
// (low-priority problems) and errors (high-priority problems) as well as an
|
|
||||||
// error value indicating that the check failed to run.
|
|
||||||
func (w *webhookCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
|
func (w *webhookCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
|
||||||
|
const apiserverServiceName = "kubernetes"
|
||||||
|
|
||||||
var diagnostics []checks.Diagnostic
|
var diagnostics []checks.Diagnostic
|
||||||
|
|
||||||
for _, config := range objects.ValidatingWebhookConfigurations.Items {
|
for _, config := range objects.ValidatingWebhookConfigurations.Items {
|
||||||
for _, validatingWebhook := range config.Webhooks {
|
for _, wh := range config.Webhooks {
|
||||||
if *validatingWebhook.FailurePolicy == ar.Fail &&
|
if *wh.FailurePolicy == ar.Ignore {
|
||||||
validatingWebhook.ClientConfig.Service != nil &&
|
// Webhooks with failurePolicy: Ignore are fine.
|
||||||
selectorMatchesNamespace(validatingWebhook.NamespaceSelector, objects.SystemNamespace) {
|
continue
|
||||||
d := checks.Diagnostic{
|
|
||||||
Severity: checks.Error,
|
|
||||||
Message: "Webhook matches objects in the kube-system namespace. This can cause problems when upgrading the cluster.",
|
|
||||||
Kind: checks.ValidatingWebhookConfiguration,
|
|
||||||
Object: &config.ObjectMeta,
|
|
||||||
Owners: config.ObjectMeta.GetOwnerReferences(),
|
|
||||||
}
|
|
||||||
diagnostics = append(diagnostics, d)
|
|
||||||
}
|
}
|
||||||
|
if wh.ClientConfig.Service == nil {
|
||||||
|
// Webhooks whose targets are external to the cluster are fine.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if wh.ClientConfig.Service.Namespace == metav1.NamespaceDefault &&
|
||||||
|
wh.ClientConfig.Service.Name == apiserverServiceName {
|
||||||
|
// Webhooks that target the kube-apiserver are fine.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !selectorMatchesNamespace(wh.NamespaceSelector, objects.SystemNamespace) {
|
||||||
|
// Webhooks that don't apply to kube-system are fine.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var svcNamespace *v1.Namespace
|
||||||
|
for _, ns := range objects.Namespaces.Items {
|
||||||
|
if ns.Name == wh.ClientConfig.Service.Namespace {
|
||||||
|
svcNamespace = &ns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if svcNamespace == nil {
|
||||||
|
return nil, errors.New("webhook refers to service in non-existent namespace")
|
||||||
|
}
|
||||||
|
if !selectorMatchesNamespace(wh.NamespaceSelector, svcNamespace) && len(objects.Nodes.Items) > 1 {
|
||||||
|
// Webhooks that don't apply to their own namespace are fine, as
|
||||||
|
// long as there's more than one node in the cluster.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d := checks.Diagnostic{
|
||||||
|
Severity: checks.Error,
|
||||||
|
Message: "Validating webhook is configured in such a way that it may be problematic during upgrades.",
|
||||||
|
Kind: checks.ValidatingWebhookConfiguration,
|
||||||
|
Object: &config.ObjectMeta,
|
||||||
|
Owners: config.ObjectMeta.GetOwnerReferences(),
|
||||||
|
}
|
||||||
|
diagnostics = append(diagnostics, d)
|
||||||
|
|
||||||
|
// We don't want to produce diagnostics for multiple webhooks in the
|
||||||
|
// same webhook configuration, so break out of the inner loop if we
|
||||||
|
// get here.
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, config := range objects.MutatingWebhookConfigurations.Items {
|
for _, config := range objects.MutatingWebhookConfigurations.Items {
|
||||||
for _, mutatingWebhook := range config.Webhooks {
|
for _, wh := range config.Webhooks {
|
||||||
if *mutatingWebhook.FailurePolicy == ar.Fail &&
|
if *wh.FailurePolicy == ar.Ignore {
|
||||||
mutatingWebhook.ClientConfig.Service != nil &&
|
// Webhooks with failurePolicy: Ignore are fine.
|
||||||
selectorMatchesNamespace(mutatingWebhook.NamespaceSelector, objects.SystemNamespace) {
|
continue
|
||||||
d := checks.Diagnostic{
|
|
||||||
Severity: checks.Error,
|
|
||||||
Message: "Webhook matches objects in the kube-system namespace. This can cause problems when upgrading the cluster.",
|
|
||||||
Kind: checks.MutatingWebhookConfiguration,
|
|
||||||
Object: &config.ObjectMeta,
|
|
||||||
Owners: config.ObjectMeta.GetOwnerReferences(),
|
|
||||||
}
|
|
||||||
diagnostics = append(diagnostics, d)
|
|
||||||
}
|
}
|
||||||
|
if wh.ClientConfig.Service == nil {
|
||||||
|
// Webhooks whose targets are external to the cluster are fine.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if wh.ClientConfig.Service.Namespace == metav1.NamespaceDefault &&
|
||||||
|
wh.ClientConfig.Service.Name == apiserverServiceName {
|
||||||
|
// Webhooks that target the kube-apiserver are fine.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !selectorMatchesNamespace(wh.NamespaceSelector, objects.SystemNamespace) {
|
||||||
|
// Webhooks that don't apply to kube-system are fine.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var svcNamespace *v1.Namespace
|
||||||
|
for _, ns := range objects.Namespaces.Items {
|
||||||
|
if ns.Name == wh.ClientConfig.Service.Namespace {
|
||||||
|
svcNamespace = &ns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if svcNamespace == nil {
|
||||||
|
return nil, errors.New("webhook refers to service in non-existent namespace")
|
||||||
|
}
|
||||||
|
if !selectorMatchesNamespace(wh.NamespaceSelector, svcNamespace) && len(objects.Nodes.Items) > 1 {
|
||||||
|
// Webhooks that don't apply to their own namespace are fine, as
|
||||||
|
// long as there's more than one node in the cluster.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d := checks.Diagnostic{
|
||||||
|
Severity: checks.Error,
|
||||||
|
Message: "Mutating webhook is configured in such a way that it may be problematic during upgrades.",
|
||||||
|
Kind: checks.MutatingWebhookConfiguration,
|
||||||
|
Object: &config.ObjectMeta,
|
||||||
|
Owners: config.ObjectMeta.GetOwnerReferences(),
|
||||||
|
}
|
||||||
|
diagnostics = append(diagnostics, d)
|
||||||
|
|
||||||
|
// We don't want to produce diagnostics for multiple webhooks in the
|
||||||
|
// same webhook configuration, so break out of the inner loop if we
|
||||||
|
// get here.
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return diagnostics, nil
|
return diagnostics, nil
|
||||||
|
|
|
@ -27,6 +27,8 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var webhookURL = "https://example.com/webhook"
|
||||||
|
|
||||||
func TestWebhookCheckMeta(t *testing.T) {
|
func TestWebhookCheckMeta(t *testing.T) {
|
||||||
webhookCheck := webhookCheck{}
|
webhookCheck := webhookCheck{}
|
||||||
assert.Equal(t, "admission-controller-webhook", webhookCheck.Name())
|
assert.Equal(t, "admission-controller-webhook", webhookCheck.Name())
|
||||||
|
@ -57,70 +59,181 @@ func TestWebhookError(t *testing.T) {
|
||||||
expected: nil,
|
expected: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "failure policy is ignore",
|
name: "failure policy is ignore",
|
||||||
objs: initObjects(ar.Ignore),
|
objs: webhookTestObjects(
|
||||||
|
ar.Ignore,
|
||||||
|
&metav1.LabelSelector{},
|
||||||
|
ar.WebhookClientConfig{
|
||||||
|
Service: &ar.ServiceReference{
|
||||||
|
Namespace: "webhook",
|
||||||
|
Name: "webhook-service",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
),
|
||||||
expected: nil,
|
expected: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "webook does not use service",
|
name: "webook does not use service",
|
||||||
objs: webhookURL(),
|
objs: webhookTestObjects(
|
||||||
|
ar.Fail,
|
||||||
|
&metav1.LabelSelector{},
|
||||||
|
ar.WebhookClientConfig{
|
||||||
|
URL: &webhookURL,
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
),
|
||||||
expected: nil,
|
expected: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "namespace selector is empty",
|
name: "webook service is apiserver",
|
||||||
objs: initObjects(ar.Fail),
|
objs: webhookTestObjects(
|
||||||
|
ar.Fail,
|
||||||
|
&metav1.LabelSelector{},
|
||||||
|
ar.WebhookClientConfig{
|
||||||
|
Service: &ar.ServiceReference{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "kubernetes",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "namespace label selector does not match kube-system",
|
||||||
|
objs: webhookTestObjects(
|
||||||
|
ar.Fail,
|
||||||
|
&metav1.LabelSelector{
|
||||||
|
MatchLabels: map[string]string{"non-existent-label-on-namespace": "bar"},
|
||||||
|
},
|
||||||
|
ar.WebhookClientConfig{
|
||||||
|
Service: &ar.ServiceReference{
|
||||||
|
Namespace: "webhook",
|
||||||
|
Name: "webhook-service",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "namespace OpExists expression selector does not match kube-system",
|
||||||
|
objs: webhookTestObjects(
|
||||||
|
ar.Fail,
|
||||||
|
&metav1.LabelSelector{
|
||||||
|
MatchExpressions: expr("non-existent", []string{}, metav1.LabelSelectorOpExists),
|
||||||
|
},
|
||||||
|
ar.WebhookClientConfig{
|
||||||
|
Service: &ar.ServiceReference{
|
||||||
|
Namespace: "webhook",
|
||||||
|
Name: "webhook-service",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "namespace OpDoesNotExist expression selector does not match kube-system",
|
||||||
|
objs: webhookTestObjects(
|
||||||
|
ar.Fail,
|
||||||
|
&metav1.LabelSelector{
|
||||||
|
MatchExpressions: expr("doks_key", []string{}, metav1.LabelSelectorOpDoesNotExist),
|
||||||
|
},
|
||||||
|
ar.WebhookClientConfig{
|
||||||
|
Service: &ar.ServiceReference{
|
||||||
|
Namespace: "webhook",
|
||||||
|
Name: "webhook-service",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "namespace OpIn expression selector does not match kube-system",
|
||||||
|
objs: webhookTestObjects(
|
||||||
|
ar.Fail,
|
||||||
|
&metav1.LabelSelector{
|
||||||
|
MatchExpressions: expr("doks_key", []string{"non-existent"}, metav1.LabelSelectorOpIn),
|
||||||
|
},
|
||||||
|
ar.WebhookClientConfig{
|
||||||
|
Service: &ar.ServiceReference{
|
||||||
|
Namespace: "webhook",
|
||||||
|
Name: "webhook-service",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "namespace OpNotIn expression selector does not match kube-system",
|
||||||
|
objs: webhookTestObjects(
|
||||||
|
ar.Fail,
|
||||||
|
&metav1.LabelSelector{
|
||||||
|
MatchExpressions: expr("doks_key", []string{"bar"}, metav1.LabelSelectorOpNotIn),
|
||||||
|
},
|
||||||
|
ar.WebhookClientConfig{
|
||||||
|
Service: &ar.ServiceReference{
|
||||||
|
Namespace: "webhook",
|
||||||
|
Name: "webhook-service",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "namespace label selector does not match own namespace",
|
||||||
|
objs: webhookTestObjects(
|
||||||
|
ar.Fail,
|
||||||
|
&metav1.LabelSelector{
|
||||||
|
MatchLabels: map[string]string{"doks_key": "bar"},
|
||||||
|
},
|
||||||
|
ar.WebhookClientConfig{
|
||||||
|
Service: &ar.ServiceReference{
|
||||||
|
Namespace: "webhook",
|
||||||
|
Name: "webhook-service",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single-node cluster",
|
||||||
|
objs: webhookTestObjects(
|
||||||
|
ar.Fail,
|
||||||
|
&metav1.LabelSelector{
|
||||||
|
MatchLabels: map[string]string{"doks_key": "bar"},
|
||||||
|
},
|
||||||
|
ar.WebhookClientConfig{
|
||||||
|
Service: &ar.ServiceReference{
|
||||||
|
Namespace: "webhook",
|
||||||
|
Name: "webhook-service",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
),
|
||||||
expected: webhookErrors(),
|
expected: webhookErrors(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "namespace selector matches label",
|
name: "webhook applies to its own namespace and kube-system",
|
||||||
objs: label(map[string]string{"doks_key": "bar"}),
|
objs: webhookTestObjects(
|
||||||
|
ar.Fail,
|
||||||
|
&metav1.LabelSelector{},
|
||||||
|
ar.WebhookClientConfig{
|
||||||
|
Service: &ar.ServiceReference{
|
||||||
|
Namespace: "webhook",
|
||||||
|
Name: "webhook-service",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
),
|
||||||
expected: webhookErrors(),
|
expected: webhookErrors(),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "namespace selector does not match label",
|
|
||||||
objs: label(map[string]string{"non-existent-label-on-namespace": "bar"}),
|
|
||||||
expected: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "namespace selector matches OpExists expression",
|
|
||||||
objs: expr("doks_key", []string{}, metav1.LabelSelectorOpExists),
|
|
||||||
expected: webhookErrors(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "namespace selector matches OpDoesNotExist expression",
|
|
||||||
objs: expr("random", []string{}, metav1.LabelSelectorOpDoesNotExist),
|
|
||||||
expected: webhookErrors(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "namespace selector matches OpIn expression",
|
|
||||||
objs: expr("doks_key", []string{"bar"}, metav1.LabelSelectorOpIn),
|
|
||||||
expected: webhookErrors(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "namespace selector matches OpNotIn expression",
|
|
||||||
objs: expr("doks_key", []string{"non-existent"}, metav1.LabelSelectorOpNotIn),
|
|
||||||
expected: webhookErrors(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "namespace selector does not match OpExists expression",
|
|
||||||
objs: expr("non-existent", []string{}, metav1.LabelSelectorOpExists),
|
|
||||||
expected: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "namespace selector does not match OpDoesNotExist expression",
|
|
||||||
objs: expr("doks_key", []string{}, metav1.LabelSelectorOpDoesNotExist),
|
|
||||||
expected: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "namespace selector does not match OpIn expression",
|
|
||||||
objs: expr("doks_key", []string{"non-existent"}, metav1.LabelSelectorOpIn),
|
|
||||||
expected: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "namespace selector does not match OpNotIn expression",
|
|
||||||
objs: expr("doks_key", []string{"bar"}, metav1.LabelSelectorOpNotIn),
|
|
||||||
expected: nil,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
webhookCheck := webhookCheck{}
|
webhookCheck := webhookCheck{}
|
||||||
|
@ -134,13 +247,45 @@ func TestWebhookError(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initObjects(failurePolicyType ar.FailurePolicyType) *kube.Objects {
|
func expr(key string, values []string, op metav1.LabelSelectorOperator) []metav1.LabelSelectorRequirement {
|
||||||
|
return []metav1.LabelSelectorRequirement{{
|
||||||
|
Key: key,
|
||||||
|
Operator: op,
|
||||||
|
Values: values,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func webhookTestObjects(
|
||||||
|
failurePolicyType ar.FailurePolicyType,
|
||||||
|
nsSelector *metav1.LabelSelector,
|
||||||
|
clientConfig ar.WebhookClientConfig,
|
||||||
|
numNodes int,
|
||||||
|
) *kube.Objects {
|
||||||
objs := &kube.Objects{
|
objs := &kube.Objects{
|
||||||
SystemNamespace: &corev1.Namespace{
|
SystemNamespace: &corev1.Namespace{
|
||||||
TypeMeta: metav1.TypeMeta{Kind: "Namespace", APIVersion: "v1"},
|
TypeMeta: metav1.TypeMeta{Kind: "Namespace", APIVersion: "v1"},
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "kube-system",
|
Name: "kube-system",
|
||||||
Labels: map[string]string{"doks_key": "bar"}},
|
Labels: map[string]string{"doks_key": "bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Namespaces: &corev1.NamespaceList{
|
||||||
|
Items: []corev1.Namespace{
|
||||||
|
{
|
||||||
|
TypeMeta: metav1.TypeMeta{Kind: "Namespace", APIVersion: "v1"},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "kube-system",
|
||||||
|
Labels: map[string]string{"doks_key": "bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TypeMeta: metav1.TypeMeta{Kind: "Namespace", APIVersion: "v1"},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "webhook",
|
||||||
|
Labels: map[string]string{"doks_key": "xyzzy"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
MutatingWebhookConfigurations: &ar.MutatingWebhookConfigurationList{
|
MutatingWebhookConfigurations: &ar.MutatingWebhookConfigurationList{
|
||||||
Items: []ar.MutatingWebhookConfiguration{
|
Items: []ar.MutatingWebhookConfiguration{
|
||||||
|
@ -153,13 +298,8 @@ func initObjects(failurePolicyType ar.FailurePolicyType) *kube.Objects {
|
||||||
{
|
{
|
||||||
Name: "mw_foo",
|
Name: "mw_foo",
|
||||||
FailurePolicy: &failurePolicyType,
|
FailurePolicy: &failurePolicyType,
|
||||||
NamespaceSelector: &metav1.LabelSelector{},
|
NamespaceSelector: nsSelector,
|
||||||
ClientConfig: ar.WebhookClientConfig{
|
ClientConfig: clientConfig,
|
||||||
Service: &ar.ServiceReference{
|
|
||||||
Name: "some-svc",
|
|
||||||
Namespace: "k8s",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -176,83 +316,38 @@ func initObjects(failurePolicyType ar.FailurePolicyType) *kube.Objects {
|
||||||
{
|
{
|
||||||
Name: "vw_foo",
|
Name: "vw_foo",
|
||||||
FailurePolicy: &failurePolicyType,
|
FailurePolicy: &failurePolicyType,
|
||||||
NamespaceSelector: &metav1.LabelSelector{},
|
NamespaceSelector: nsSelector,
|
||||||
ClientConfig: ar.WebhookClientConfig{
|
ClientConfig: clientConfig,
|
||||||
Service: &ar.ServiceReference{
|
|
||||||
Name: "some-svc",
|
|
||||||
Namespace: "k8s",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return objs
|
|
||||||
}
|
|
||||||
|
|
||||||
func webhookURL() *kube.Objects {
|
objs.Nodes = &corev1.NodeList{}
|
||||||
var url = "https://example.com/webhook/action"
|
for i := 0; i < numNodes; i++ {
|
||||||
objs := initObjects(ar.Fail)
|
objs.Nodes.Items = append(objs.Nodes.Items, corev1.Node{})
|
||||||
objs.ValidatingWebhookConfigurations.Items[0].Webhooks[0].ClientConfig = ar.WebhookClientConfig{
|
|
||||||
URL: &url,
|
|
||||||
}
|
|
||||||
objs.MutatingWebhookConfigurations.Items[0].Webhooks[0].ClientConfig = ar.WebhookClientConfig{
|
|
||||||
URL: &url,
|
|
||||||
}
|
|
||||||
return objs
|
|
||||||
}
|
|
||||||
|
|
||||||
func label(label map[string]string) *kube.Objects {
|
|
||||||
objs := initObjects(ar.Fail)
|
|
||||||
objs.ValidatingWebhookConfigurations.Items[0].Webhooks[0].NamespaceSelector = &metav1.LabelSelector{
|
|
||||||
MatchLabels: label,
|
|
||||||
}
|
|
||||||
objs.MutatingWebhookConfigurations.Items[0].Webhooks[0].NamespaceSelector = &metav1.LabelSelector{
|
|
||||||
MatchLabels: label,
|
|
||||||
}
|
|
||||||
return objs
|
|
||||||
}
|
|
||||||
|
|
||||||
func expr(key string, values []string, labelOperator metav1.LabelSelectorOperator) *kube.Objects {
|
|
||||||
objs := initObjects(ar.Fail)
|
|
||||||
objs.ValidatingWebhookConfigurations.Items[0].Webhooks[0].NamespaceSelector = &metav1.LabelSelector{
|
|
||||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
|
||||||
{
|
|
||||||
Key: key,
|
|
||||||
Operator: labelOperator,
|
|
||||||
Values: values,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
objs.MutatingWebhookConfigurations.Items[0].Webhooks[0].NamespaceSelector = &metav1.LabelSelector{
|
|
||||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
|
||||||
{
|
|
||||||
Key: key,
|
|
||||||
Operator: labelOperator,
|
|
||||||
Values: values,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
return objs
|
return objs
|
||||||
}
|
}
|
||||||
|
|
||||||
func webhookErrors() []checks.Diagnostic {
|
func webhookErrors() []checks.Diagnostic {
|
||||||
objs := initObjects(ar.Fail)
|
objs := webhookTestObjects(ar.Fail, nil, ar.WebhookClientConfig{}, 0)
|
||||||
validatingConfig := objs.ValidatingWebhookConfigurations.Items[0]
|
validatingConfig := objs.ValidatingWebhookConfigurations.Items[0]
|
||||||
mutatingConfig := objs.MutatingWebhookConfigurations.Items[0]
|
mutatingConfig := objs.MutatingWebhookConfigurations.Items[0]
|
||||||
|
|
||||||
diagnostics := []checks.Diagnostic{
|
diagnostics := []checks.Diagnostic{
|
||||||
{
|
{
|
||||||
Severity: checks.Error,
|
Severity: checks.Error,
|
||||||
Message: "Webhook matches objects in the kube-system namespace. This can cause problems when upgrading the cluster.",
|
Message: "Validating webhook is configured in such a way that it may be problematic during upgrades.",
|
||||||
Kind: checks.ValidatingWebhookConfiguration,
|
Kind: checks.ValidatingWebhookConfiguration,
|
||||||
Object: &validatingConfig.ObjectMeta,
|
Object: &validatingConfig.ObjectMeta,
|
||||||
Owners: validatingConfig.ObjectMeta.GetOwnerReferences(),
|
Owners: validatingConfig.ObjectMeta.GetOwnerReferences(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Severity: checks.Error,
|
Severity: checks.Error,
|
||||||
Message: "Webhook matches objects in the kube-system namespace. This can cause problems when upgrading the cluster.",
|
Message: "Mutating webhook is configured in such a way that it may be problematic during upgrades.",
|
||||||
Kind: checks.MutatingWebhookConfiguration,
|
Kind: checks.MutatingWebhookConfiguration,
|
||||||
Object: &mutatingConfig.ObjectMeta,
|
Object: &mutatingConfig.ObjectMeta,
|
||||||
Owners: mutatingConfig.ObjectMeta.GetOwnerReferences(),
|
Owners: mutatingConfig.ObjectMeta.GetOwnerReferences(),
|
||||||
|
|
|
@ -56,7 +56,7 @@ func TestNodeNameError(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "node name used in node selector",
|
name: "node name used in node selector",
|
||||||
objs: invalidPod(),
|
objs: invalidPod(),
|
||||||
expected: warnings(invalidPod(), podSelectorCheck.Name()),
|
expected: expectedWarnings(invalidPod(), podSelectorCheck.Name()),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ func invalidPod() *kube.Objects {
|
||||||
return objs
|
return objs
|
||||||
}
|
}
|
||||||
|
|
||||||
func warnings(objs *kube.Objects, name string) []checks.Diagnostic {
|
func expectedWarnings(objs *kube.Objects, name string) []checks.Diagnostic {
|
||||||
pod := objs.Pods.Items[0]
|
pod := objs.Pods.Items[0]
|
||||||
diagnostics := []checks.Diagnostic{
|
diagnostics := []checks.Diagnostic{
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,6 +54,7 @@ type Objects struct {
|
||||||
LimitRanges *corev1.LimitRangeList
|
LimitRanges *corev1.LimitRangeList
|
||||||
MutatingWebhookConfigurations *ar.MutatingWebhookConfigurationList
|
MutatingWebhookConfigurations *ar.MutatingWebhookConfigurationList
|
||||||
ValidatingWebhookConfigurations *ar.ValidatingWebhookConfigurationList
|
ValidatingWebhookConfigurations *ar.ValidatingWebhookConfigurationList
|
||||||
|
Namespaces *corev1.NamespaceList
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client encapsulates a client for a Kubernetes cluster.
|
// Client encapsulates a client for a Kubernetes cluster.
|
||||||
|
@ -131,6 +132,10 @@ func (c *Client) FetchObjects(ctx context.Context) (*Objects, error) {
|
||||||
objects.ValidatingWebhookConfigurations, err = admissionControllerClient.ValidatingWebhookConfigurations().List(opts)
|
objects.ValidatingWebhookConfigurations, err = admissionControllerClient.ValidatingWebhookConfigurations().List(opts)
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
g.Go(func() (err error) {
|
||||||
|
objects.Namespaces, err = client.Namespaces().List(opts)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
err := g.Wait()
|
err := g.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue