webhooks/core/node_update_webhook.go (63 lines of code) (raw):
package core
import (
"context"
"net/http"
"reflect"
"github.com/aws/amazon-vpc-resource-controller-k8s/pkg/condition"
"github.com/aws/amazon-vpc-resource-controller-k8s/pkg/config"
rcHealthz "github.com/aws/amazon-vpc-resource-controller-k8s/pkg/healthz"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
type NodeUpdateWebhook struct {
decoder admission.Decoder
Condition condition.Conditions
Log logr.Logger
Checker healthz.Checker
}
func NewNodeUpdateWebhook(condition condition.Conditions, log logr.Logger, d admission.Decoder, healthzHandler *rcHealthz.HealthzHandler) *NodeUpdateWebhook {
nodeUpdateWebhook := &NodeUpdateWebhook{
Condition: condition,
Log: log,
decoder: d,
}
// add health check on subpath for node validation webhook
healthzHandler.AddControllersHealthCheckers(
map[string]healthz.Checker{
"health-node-validating-webhook": rcHealthz.SimplePing("node validating webhook", log),
},
)
return nodeUpdateWebhook
}
const awsNodeUsername = "system:serviceaccount:kube-system:aws-node"
// +kubebuilder:webhook:path=/validate-v1-node,mutating=false,matchPolicy=Equivalent,failurePolicy=ignore,groups="",resources=nodes,verbs=update,versions=v1,name=vnode.vpc.k8s.aws,sideEffects=None,admissionReviewVersions=v1
// Handle allows update request on Node on the expected fields when the request is
// coming from the aws-node Service Account. It also ensures the updates are allowed only
// when the Security Group for Pod feature is enabled.
func (a *NodeUpdateWebhook) Handle(_ context.Context, req admission.Request) admission.Response {
// Allow all requests that are not from aws-node username
if req.UserInfo.Username != awsNodeUsername {
return admission.Allowed("")
}
logger := a.Log.WithValues("node", req.Name)
logger.Info("update request received from aws-node")
newNode := &corev1.Node{}
if err := a.decoder.DecodeRaw(req.Object, newNode); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
oldNode := &corev1.Node{}
if err := a.decoder.DecodeRaw(req.OldObject, oldNode); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
// Remove the values that we expect the aws-node is supposed to modify
delete(oldNode.Labels, config.HasTrunkAttachedLabel)
delete(newNode.Labels, config.HasTrunkAttachedLabel)
delete(oldNode.Labels, config.CustomNetworkingLabel)
delete(newNode.Labels, config.CustomNetworkingLabel)
// The new object has the ManagedFields which is missing from older object, so remove it as well
oldNode.ManagedFields = nil
newNode.ManagedFields = nil
// Required for v1.18 clusters
oldNode.SelfLink = ""
newNode.SelfLink = ""
// Deny request if there's any modification in the old and new object after removing the fields
// added by aws-node
if !reflect.DeepEqual(*newNode, *oldNode) {
denyMessage := "aws-node can only update limited fields on the Node Object"
// Keep log to Debug as it prints entire object
logger.V(1).Info("request will be denied", "old object", *oldNode, "new object", *newNode)
logger.Info(denyMessage)
return admission.Denied(denyMessage)
}
// If all validation check succeed, allow admission
return admission.Allowed("")
}