Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(kuma-cp) Secrets validation on K8S #696

Merged
merged 2 commits into from
Apr 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# CHANGELOG

## master
* feat include traffic direction in access log
* feat: secret validation on K8S
[#696](https://github.com/Kong/kuma/pull/696)
* feat: include traffic direction in access log
[#682](https://github.com/Kong/kuma/pull/682)
* feat: validate tags and selectors
[#691](https://github.com/Kong/kuma/pull/691)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4680,3 +4680,21 @@ webhooks:
- UPDATE
resources:
- services
- name: secret.validator.kuma-admission.kuma.io
failurePolicy: Fail
clientConfig:
caBundle: Q0VSVA==
service:
namespace: kuma-system
name: kuma-control-plane
path: /validate-v1-secret
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- secrets
Original file line number Diff line number Diff line change
Expand Up @@ -4534,3 +4534,21 @@ webhooks:
- UPDATE
resources:
- services
- name: secret.validator.kuma-admission.kuma.io
failurePolicy: Fail
clientConfig:
caBundle: Q0VSVA==
service:
namespace: kuma-system
name: kuma-control-plane
path: /validate-v1-secret
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- secrets
Original file line number Diff line number Diff line change
Expand Up @@ -4535,3 +4535,21 @@ webhooks:
- UPDATE
resources:
- services
- name: secret.validator.kuma-admission.kuma.io
failurePolicy: Fail
clientConfig:
caBundle: QWRtaXNzaW9uQ2VydA==
service:
namespace: kuma
name: kuma-ctrl-plane
path: /validate-v1-secret
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- secrets
18 changes: 18 additions & 0 deletions app/kumactl/data/install/k8s/control-plane/kuma-cp/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,21 @@ webhooks:
- UPDATE
resources:
- services
- name: secret.validator.kuma-admission.kuma.io
failurePolicy: Fail
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question arose from my merging injector and kuma-cp:
Why injector.failurePolicy is parametrized and by default equals Ignore, unlike all other webhooks, have failurePolicy hardcoded and equal Fail?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not the original author of injector, but my guess is that we don't want to stop the deployment even if Kuma DP fails to be injected.

clientConfig:
caBundle: {{ .AdmissionServerTlsCert | b64enc }}
service:
namespace: {{ .Namespace }}
name: {{ .ControlPlaneServiceName }}
path: /validate-v1-secret
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- secrets
42 changes: 21 additions & 21 deletions app/kumactl/pkg/install/k8s/control-plane/templates_vfsdata.go

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions pkg/plugins/runtime/k8s/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,11 @@ func addValidators(mgr kube_ctrl.Manager, rt core_runtime.Runtime) error {
mgr.GetWebhookServer().Register("/validate-v1-service", &kuba_webhook.Admission{Handler: &k8s_webhooks.ServiceValidator{}})
log.Info("Registering a validation webhook for v1/Service", "path", "/validate-v1-service")

secretValidator := &k8s_webhooks.SecretValidator{
Client: mgr.GetClient(),
}
mgr.GetWebhookServer().Register("/validate-v1-secret", &kuba_webhook.Admission{Handler: secretValidator})
log.Info("Registering a validation webhook for v1/Secret", "path", "/validate-v1-secret")

return nil
}
4 changes: 2 additions & 2 deletions pkg/plugins/runtime/k8s/webhooks/mesh_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (h *MeshValidator) ValidateCreate(ctx context.Context, req admission.Reques
}
if err := h.validator.ValidateCreate(ctx, req.Name, coreRes); err != nil {
if kumaErr, ok := err.(*validators.ValidationError); ok {
return convertValidationError(kumaErr, k8sRes)
return convertSpecValidationError(kumaErr, k8sRes)
}
return admission.Denied(err.Error())
}
Expand Down Expand Up @@ -82,7 +82,7 @@ func (h *MeshValidator) ValidateUpdate(ctx context.Context, req admission.Reques

if err := h.validator.ValidateUpdate(ctx, oldCoreRes, coreRes); err != nil {
if kumaErr, ok := err.(*validators.ValidationError); ok {
return convertValidationError(kumaErr, k8sRes)
return convertSpecValidationError(kumaErr, k8sRes)
}
return admission.Denied(err.Error())
}
Expand Down
104 changes: 104 additions & 0 deletions pkg/plugins/runtime/k8s/webhooks/secret_validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package webhooks

import (
"context"
"net/http"

"github.com/pkg/errors"

"github.com/Kong/kuma/pkg/core/validators"
mesh_k8s "github.com/Kong/kuma/pkg/plugins/resources/k8s/native/api/v1alpha1"

kube_core "k8s.io/api/core/v1"
kube_apierrs "k8s.io/apimachinery/pkg/api/errors"
kube_types "k8s.io/apimachinery/pkg/types"
kube_client "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

const (
meshLabel = "kuma.io/mesh"
)

type SecretValidator struct {
Decoder *admission.Decoder
Client kube_client.Client
}

func (v *SecretValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
secret := &kube_core.Secret{}

err := v.Decoder.Decode(req, secret)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}

oldSecret := &kube_core.Secret{}
if len(req.OldObject.Raw) != 0 {
err := v.Decoder.DecodeRaw(req.OldObject, oldSecret)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
} else {
oldSecret = nil
}

if err := v.validate(ctx, secret, oldSecret); err != nil {
if verr, ok := err.(*validators.ValidationError); ok {
return convertValidationErrorOf(*verr, secret, secret)
}
return admission.Denied(err.Error())
}

return admission.Allowed("")
}

func (v *SecretValidator) validate(ctx context.Context, secret *kube_core.Secret, oldSecret *kube_core.Secret) error {
verr := &validators.ValidationError{}
if !isKumaSecret(secret) {
return nil
}

// validate mesh exists
mesh := mesh_k8s.Mesh{}
key := kube_types.NamespacedName{
Name: meshOfSecret(secret),
}
if err := v.Client.Get(ctx, key, &mesh); err != nil {
if kube_apierrs.IsNotFound(err) {
verr.AddViolationAt(validators.RootedAt("metadata").Field("labels").Key(meshLabel), "mesh does not exist")
} else {
return errors.Wrap(err, "could not fetch mesh")
}
}

// block change of the mesh on the secret
if oldSecret != nil {
if meshOfSecret(secret) != meshOfSecret(oldSecret) {
verr.AddViolationAt(validators.RootedAt("metadata").Field("labels").Key(meshLabel), "cannot change mesh of the Secret. Delete the Secret first and apply it again.")
}
}

// validate convention of the secret
if len(secret.Data["value"]) == 0 {
verr.AddViolationAt(validators.RootedAt("data").Field("value"), "cannot be empty.")
}
return verr.OrNil()
}

func isKumaSecret(secret *kube_core.Secret) bool {
return secret.Type == "system.kuma.io/secret"
}

func meshOfSecret(secret *kube_core.Secret) string {
meshName := secret.GetLabels()[meshLabel]
if meshName == "" {
return "default"
}
return meshName
}

func (v *SecretValidator) InjectDecoder(d *admission.Decoder) error {
v.Decoder = d
return nil
}
Loading