Skip to content

Commit

Permalink
Merge pull request #181 from israel-hdez/jira-2542-fix
Browse files Browse the repository at this point in the history
Prevent creation of KSVC if its namespace is not in the Mesh
  • Loading branch information
openshift-merge-bot[bot] authored Mar 21, 2024
2 parents 148960f + 6a1dd19 commit 3904c1d
Show file tree
Hide file tree
Showing 13 changed files with 360 additions and 11 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ help: ## Display this help.

.PHONY: manifests
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
$(CONTROLLER_GEN) rbac:roleName=odh-model-controller-role,headerFile="hack/manifests_boilerplate.yaml.txt" crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
$(CONTROLLER_GEN) rbac:roleName=odh-model-controller-role,headerFile="hack/manifests_boilerplate.yaml.txt" crd paths="./..." output:crd:artifacts:config=config/crd/bases

external-manifests:
go get github.com/kserve/modelmesh-serving
Expand Down
1 change: 1 addition & 0 deletions config/default/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
bases:
- ../rbac
- ../manager
- ../webhook
13 changes: 13 additions & 0 deletions config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,18 @@ spec:
requests:
cpu: 10m
memory: 64Mi
ports:
- containerPort: 9443
name: webhook-server
protocol: TCP
volumeMounts:
- mountPath: /tmp/k8s-webhook-server/serving-certs
name: cert
readOnly: true
serviceAccountName: odh-model-controller
terminationGracePeriodSeconds: 10
volumes:
- name: cert
secret:
defaultMode: 420
secretName: odh-model-controller-webhook-cert
6 changes: 6 additions & 0 deletions config/webhook/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- manifests.yaml
- service.yaml
26 changes: 26 additions & 0 deletions config/webhook/manifests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validating.odh-model-controller.opendatahub.io
annotations:
service.beta.openshift.io/inject-cabundle: true
webhooks:
- admissionReviewVersions:
- v1
clientConfig:
service:
name: odh-model-controller-webhook-service
path: /validate-serving-knative-dev-v1-service
failurePolicy: Fail
name: validating.ksvc.odh-model-controller.opendatahub.io
rules:
- apiGroups:
- serving.knative.dev
apiVersions:
- v1
operations:
- CREATE
resources:
- services
sideEffects: None
12 changes: 12 additions & 0 deletions config/webhook/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: odh-model-controller-webhook-service
annotations:
service.beta.openshift.io/serving-cert-secret-name: odh-model-controller-webhook-cert
spec:
ports:
- port: 443
targetPort: webhook-server
selector:
control-plane: odh-model-controller
4 changes: 4 additions & 0 deletions controllers/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@ limitations under the License.
package constants

const (
InferenceServiceKind = "InferenceService"

IstioNamespace = "istio-system"
ServiceMeshMemberRollName = "default"
IstioIngressService = "istio-ingressgateway"
IstioIngressServiceHTTPPortName = "http2"
IstioIngressServiceHTTPSPortName = "https"
IstioSidecarInjectAnnotationName = "sidecar.istio.io/inject"

LabelAuthGroup = "security.opendatahub.io/authorization-group"
LabelEnableAuthODH = "security.opendatahub.io/enable-auth"
Expand Down
12 changes: 4 additions & 8 deletions controllers/reconcilers/kserve_istio_smmr_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
serviceMeshMemberRollName = "default"
)

type KserveIstioSMMRReconciler struct {
client client.Client
scheme *runtime.Scheme
Expand Down Expand Up @@ -72,7 +68,7 @@ func (r *KserveIstioSMMRReconciler) Reconcile(ctx context.Context, log logr.Logg
}

func (r *KserveIstioSMMRReconciler) createDesiredResource(ctx context.Context, log logr.Logger, isvc *kservev1beta1.InferenceService) (*v1.ServiceMeshMemberRoll, error) {
desiredSMMR, err := r.smmrHandler.FetchSMMR(ctx, log, types.NamespacedName{Name: serviceMeshMemberRollName, Namespace: constants.IstioNamespace})
desiredSMMR, err := r.smmrHandler.FetchSMMR(ctx, log, types.NamespacedName{Name: constants.ServiceMeshMemberRollName, Namespace: constants.IstioNamespace})
if err != nil {
return nil, err
}
Expand All @@ -92,7 +88,7 @@ func (r *KserveIstioSMMRReconciler) createDesiredResource(ctx context.Context, l
} else {
desiredSMMR = &v1.ServiceMeshMemberRoll{
ObjectMeta: metav1.ObjectMeta{
Name: serviceMeshMemberRollName,
Name: constants.ServiceMeshMemberRollName,
Namespace: constants.IstioNamespace,
},
Spec: v1.ServiceMeshMemberRollSpec{
Expand All @@ -106,7 +102,7 @@ func (r *KserveIstioSMMRReconciler) createDesiredResource(ctx context.Context, l
}

func (r *KserveIstioSMMRReconciler) getExistingResource(ctx context.Context, log logr.Logger) (*v1.ServiceMeshMemberRoll, error) {
return r.smmrHandler.FetchSMMR(ctx, log, types.NamespacedName{Name: serviceMeshMemberRollName, Namespace: constants.IstioNamespace})
return r.smmrHandler.FetchSMMR(ctx, log, types.NamespacedName{Name: constants.ServiceMeshMemberRollName, Namespace: constants.IstioNamespace})
}

func (r *KserveIstioSMMRReconciler) processDelta(ctx context.Context, log logr.Logger, desiredSMMR *v1.ServiceMeshMemberRoll, existingSMMR *v1.ServiceMeshMemberRoll) (err error) {
Expand Down Expand Up @@ -143,5 +139,5 @@ func (r *KserveIstioSMMRReconciler) processDelta(ctx context.Context, log logr.L
}

func (r *KserveIstioSMMRReconciler) RemoveMemberFromSMMR(ctx context.Context, isvcNamespace string) error {
return r.smmrHandler.RemoveMemberFromSMMR(ctx, types.NamespacedName{Name: serviceMeshMemberRollName, Namespace: constants.IstioNamespace}, isvcNamespace)
return r.smmrHandler.RemoveMemberFromSMMR(ctx, types.NamespacedName{Name: constants.ServiceMeshMemberRollName, Namespace: constants.IstioNamespace}, isvcNamespace)
}
5 changes: 3 additions & 2 deletions controllers/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"testing"
"time"

"github.com/opendatahub-io/odh-model-controller/controllers/constants"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

kservev1alpha1 "github.com/kserve/kserve/pkg/apis/serving/v1alpha1"
Expand All @@ -37,14 +36,16 @@ import (

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/opendatahub-io/odh-model-controller/controllers/utils"
routev1 "github.com/openshift/api/route/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"

"github.com/opendatahub-io/odh-model-controller/controllers/constants"
"github.com/opendatahub-io/odh-model-controller/controllers/utils"
//+kubebuilder:scaffold:imports
)

Expand Down
2 changes: 2 additions & 0 deletions controllers/utils/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
corev1 "k8s.io/api/core/v1"
authv1 "k8s.io/api/rbac/v1"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
knservingv1 "knative.dev/serving/pkg/apis/serving/v1"
maistrav1 "maistra.io/api/core/v1"

"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -31,6 +32,7 @@ func RegisterSchemes(s *runtime.Scheme) {
utilruntime.Must(istiosecurityv1beta1.AddToScheme(s))
utilruntime.Must(telemetryv1alpha1.AddToScheme(s))
utilruntime.Must(maistrav1.SchemeBuilder.AddToScheme(s))
utilruntime.Must(knservingv1.AddToScheme(s))

// The following are related to Service Mesh, uncomment this and other
// similar blocks to use with Service Mesh
Expand Down
109 changes: 109 additions & 0 deletions controllers/webhook/ksvc_validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package webhook

import (
"context"
"fmt"
"strings"

kservev1beta1 "github.com/kserve/kserve/pkg/apis/serving/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
types2 "k8s.io/apimachinery/pkg/types"
knservingv1 "knative.dev/serving/pkg/apis/serving/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"

"github.com/opendatahub-io/odh-model-controller/controllers/constants"
"github.com/opendatahub-io/odh-model-controller/controllers/resources"
)

// +kubebuilder:webhook:admissionReviewVersions=v1,path=/validate-serving-knative-dev-v1-service,mutating=false,failurePolicy=fail,groups="serving.knative.dev",resources=services,verbs=create,versions=v1,name=validating.ksvc.odh-model-controller.opendatahub.io,sideEffects=None

type ksvcValidator struct {
client client.Client
}

func NewKsvcValidator(client client.Client) *ksvcValidator {
return &ksvcValidator{client: client}
}

func (v *ksvcValidator) ValidateCreate(ctx context.Context, obj runtime.Object) error {
log := logf.FromContext(ctx).WithName("KsvcValidatingWebhook")
ksvc, ok := obj.(*knservingv1.Service)
if !ok {
return fmt.Errorf("expected a Knative Service but got a %T", obj)
}

log = log.WithValues("namespace", ksvc.Namespace, "ksvc", ksvc.Name)
log.Info("Validating Knative Service")

// If there is an explicit intent for not having a sidecar, skip validation
ksvcTemplateMeta := ksvc.Spec.Template.GetObjectMeta()
if ksvcTemplateMeta != nil {
if templateAnnotations := ksvcTemplateMeta.GetAnnotations(); templateAnnotations[constants.IstioSidecarInjectAnnotationName] == "false" {
log.V(1).Info("Skipping validation of Knative Service because there is an explicit intent to exclude it from the Service Mesh")
return nil
}
}

// Only validate the KSVC if it is owned by KServe controller
ksvcMetadata := ksvc.GetObjectMeta()
if ksvcMetadata == nil {
log.V(1).Info("Skipping validation of Knative Service because it does not have metadata")
return nil
}
ksvcOwnerReferences := ksvcMetadata.GetOwnerReferences()
if ksvcOwnerReferences == nil {
log.V(1).Info("Skipping validation of Knative Service because it does not have owner references")
return nil
}
isOwnedByKServe := false
for _, owner := range ksvcOwnerReferences {
if owner.Kind == constants.InferenceServiceKind {
if strings.Contains(owner.APIVersion, kservev1beta1.SchemeGroupVersion.Group) {
isOwnedByKServe = true
}
}
}
if !isOwnedByKServe {
log.V(1).Info("Skipping validation of Knative Service because it is not owned by KServe")
return nil
}

// Since the Ksvc is owned by an InferenceService, it is known that it is required to be
// in the Mesh. Thus, the involved namespace needs to be enrolled in the mesh.
// Go and check the ServiceMeshMemberRoll to verify that the namespace is already a
// member. If it is still not a member, reject creation of the Ksvc to prevent
// creation of a Pod that would not be in the Mesh.
smmrQuerier := resources.NewServiceMeshMemberRole(v.client)
smmr, fetchSmmrErr := smmrQuerier.FetchSMMR(ctx, log, types2.NamespacedName{Name: constants.ServiceMeshMemberRollName, Namespace: constants.IstioNamespace})
if fetchSmmrErr != nil {
log.Error(fetchSmmrErr, "Error when fetching ServiceMeshMemberRoll", "smmr.namespace", constants.IstioNamespace, "smmr.name", constants.ServiceMeshMemberRollName)
return fetchSmmrErr
}
if smmr == nil {
log.Info("Rejecting Knative service because the ServiceMeshMemberRoll does not exist", "smmr.namespace", constants.IstioNamespace, "smmr.name", constants.ServiceMeshMemberRollName)
return fmt.Errorf("rejecting creation of Knative service %s on namespace %s because the ServiceMeshMemberRoll does not exist", ksvc.Name, ksvc.Namespace)
}

log = log.WithValues("smmr.namespace", smmr.Namespace, "smmr.name", smmr.Name)

for _, memberNamespace := range smmr.Status.ConfiguredMembers {
if memberNamespace == ksvc.Namespace {
log.V(1).Info("The Knative service is accepted")
return nil
}
}

log.Info("Rejecting Knative service because its namespace is not a member of the service mesh")
return fmt.Errorf("rejecting creation of Knative service %s on namespace %s because the namespace is not a configured member of the mesh", ksvc.Name, ksvc.Namespace)
}

func (v *ksvcValidator) ValidateUpdate(_ context.Context, _, _ runtime.Object) error {
// Nothing to validate on updates
return nil
}

func (v *ksvcValidator) ValidateDelete(_ context.Context, _ runtime.Object) error {
// For deletion, we don't need to validate anything.
return nil
}
Loading

0 comments on commit 3904c1d

Please sign in to comment.