From 935f24fc9596319fc2e24a7552eec292d3e966dd Mon Sep 17 00:00:00 2001 From: Dhiraj Bokde Date: Thu, 4 Apr 2024 12:07:37 -0700 Subject: [PATCH] feat: add istio config and odh overlay (#64) * feat: add istio config and odh overlay * feat: initial version of istio resources and crd changes * feat: added tls and non-tls istio samples, refactored and cleaned up templates and operator * feat: add support for optional port number in gateway authority header * fix: added istio-system namespace for make certificates * feat: Updated README with better instructions, and for using Istio samples * Fix comment in modelregistry_types.go Co-authored-by: Wen Zhou * fix: regenerate updated manifests after crd changes * chore: use descriptive import aliases * feat: remove odh istio config since it will be created in opendatahub-operator instead * fix: remove non-essential gateway properties from CRD * fix: added ns to certificates/clean target * fix: add wait for sample db start * fix: bump initialDelaySeconds for model registry deployment to avoid too many restarts * fix: add missing operator rbac permissions * fix: fix rbac role generation * fix: role needs to be created before being referenced in openshift * feat: fixes RHOAIENG-4218, added standard app labels for all k8s resources * feat: fixes RHOAIENG-4218, added role annotations for display-name and description * fix: add ENABLE_WEBHOOKS=false by default for 'make runr' * chore: clean up README instructions for using Istio samples * fix: make istioIngress optional * chore: added instructions in README to enabled OpenShift Service Mesh gateway route creation --------- Co-authored-by: Wen Zhou --- .gitignore | 3 + Makefile | 30 +- README.md | 180 ++++++++++- api/v1alpha1/modelregistry_types.go | 113 ++++++- api/v1alpha1/modelregistry_webhook.go | 45 ++- api/v1alpha1/zz_generated.deepcopy.go | 109 +++++++ cmd/main.go | 43 ++- ...gistry.opendatahub.io_modelregistries.yaml | 189 +++++++++++ config/crd/kustomization.yaml | 2 +- config/rbac/role.yaml | 96 ++++++ .../istio/components/authconfig-labels.yaml | 4 + config/samples/istio/components/istio.env | 5 + .../istio/components/istio_modelregistry.yaml | 13 + .../istio/components/kustomization.yaml | 26 ++ .../istio/components/replacements.yaml | 32 ++ .../tls/istio_tls_modelregistry.yaml | 13 + .../istio/components/tls/kustomization.yaml | 12 + .../components/tls/tls-replacements.yaml | 21 ++ .../istio/mysql-tls/kustomization.yaml | 10 + config/samples/istio/mysql/kustomization.yaml | 10 + .../istio/postgres-tls/kustomization.yaml | 10 + .../samples/istio/postgres/kustomization.yaml | 10 + config/samples/mysql/mysql-db.yaml | 4 +- config/samples/postgres/postgres-db.yaml | 4 +- go.mod | 10 +- go.sum | 22 +- internal/controller/config/defaults.go | 3 +- internal/controller/config/defaults_test.go | 41 +++ .../config/templates/deployment.yaml.tmpl | 17 +- .../config/templates/group.yaml.tmpl | 13 + .../config/templates/http-route.yaml.tmpl | 6 + .../templates/istio/authconfig.yaml.tmpl | 59 ++++ .../authorino-authorization-policy.yaml.tmpl | 24 ++ .../istio/destination-rule.yaml.tmpl | 19 ++ .../config/templates/istio/gateway.yaml.tmpl | 49 +++ .../templates/istio/virtual-service.yaml.tmpl | 62 ++++ .../config/templates/role-binding.yaml.tmpl | 22 ++ .../config/templates/role.yaml.tmpl | 26 ++ .../config/templates/service.yaml.tmpl | 8 + .../config/templates/serviceaccount.yaml.tmpl | 6 + .../controller/modelregistry_controller.go | 301 ++++++++++++++++-- scripts/generate_certs.sh | 12 + 42 files changed, 1635 insertions(+), 49 deletions(-) create mode 100644 config/samples/istio/components/authconfig-labels.yaml create mode 100644 config/samples/istio/components/istio.env create mode 100644 config/samples/istio/components/istio_modelregistry.yaml create mode 100644 config/samples/istio/components/kustomization.yaml create mode 100644 config/samples/istio/components/replacements.yaml create mode 100644 config/samples/istio/components/tls/istio_tls_modelregistry.yaml create mode 100644 config/samples/istio/components/tls/kustomization.yaml create mode 100644 config/samples/istio/components/tls/tls-replacements.yaml create mode 100644 config/samples/istio/mysql-tls/kustomization.yaml create mode 100644 config/samples/istio/mysql/kustomization.yaml create mode 100644 config/samples/istio/postgres-tls/kustomization.yaml create mode 100644 config/samples/istio/postgres/kustomization.yaml create mode 100644 internal/controller/config/templates/group.yaml.tmpl create mode 100644 internal/controller/config/templates/istio/authconfig.yaml.tmpl create mode 100644 internal/controller/config/templates/istio/authorino-authorization-policy.yaml.tmpl create mode 100644 internal/controller/config/templates/istio/destination-rule.yaml.tmpl create mode 100644 internal/controller/config/templates/istio/gateway.yaml.tmpl create mode 100644 internal/controller/config/templates/istio/virtual-service.yaml.tmpl create mode 100644 internal/controller/config/templates/role-binding.yaml.tmpl create mode 100644 internal/controller/config/templates/role.yaml.tmpl create mode 100755 scripts/generate_certs.sh diff --git a/.gitignore b/.gitignore index 7f02333..a9f565c 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ Dockerfile.cross *.swp *.swo *~ + +# gateway TLS certificates +certs/** diff --git a/Makefile b/Makefile index 8dd9730..3d65287 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ # Include images as env variables include ./config/overlays/odh/params.env +# Include istio config as env variables +include ./config/samples/istio/components/istio.env + # Image URL to use all building/pushing image targets IMG_REGISTRY ?= "quay.io" IMG_ORG ?= "opendatahub" @@ -13,6 +16,9 @@ ENVTEST_K8S_VERSION = 1.28.0 # Kustomize overlay to use for deploy/undeploy OVERLAY ?= "default" +# Disable operator webhooks by default for local testing +ENABLE_WEBHOOKS ?= false + # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) GOBIN=$(shell go env GOPATH)/bin @@ -90,7 +96,7 @@ build: sync-images manifests generate fmt vet ## Build manager binary. .PHONY: run run: manifests generate fmt vet ## Run a controller from your host. - go run ./cmd/main.go + ENABLE_WEBHOOKS=$(ENABLE_WEBHOOKS) go run ./cmd/main.go # If you wish to build the manager image targeting other platforms you can use the --platform flag. # (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. @@ -184,3 +190,25 @@ $(CONTROLLER_GEN): $(LOCALBIN) envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. $(ENVTEST): $(LOCALBIN) test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@$(ENVTEST_VERSION) + +.PHONY: certificates +certificates: + # generate TLS certs + scripts/generate_certs.sh $(DOMAIN) + # create secrets from TLS certs + $(KUBECTL) create secret -n istio-system generic modelregistry-sample-rest-credential \ + --from-file=tls.key=certs/modelregistry-sample-rest.domain.key \ + --from-file=tls.crt=certs/modelregistry-sample-rest.domain.crt \ + --from-file=ca.crt=certs/domain.crt + $(KUBECTL) create secret -n istio-system generic modelregistry-sample-grpc-credential \ + --from-file=tls.key=certs/modelregistry-sample-grpc.domain.key \ + --from-file=tls.crt=certs/modelregistry-sample-grpc.domain.crt \ + --from-file=ca.crt=certs/domain.crt + +.PHONY: certificates/clean +certificates/clean: + # delete cert files + mkdir -p certs + rm -f certs/* + # delete k8s secrets + $(KUBECTL) delete -n istio-system secrets modelregistry-sample-rest-credential modelregistry-sample-grpc-credential diff --git a/README.md b/README.md index 26aed73..0b3b052 100644 --- a/README.md +++ b/README.md @@ -11,33 +11,193 @@ The controller reconciles `ModelRegistry` Custom Resources to create a service f You’ll need a Kubernetes cluster to run against. You can use [KIND](https://sigs.k8s.io/kind) to get a local cluster for testing, or run against a remote cluster. **Note:** Your controller will automatically use the current context in your kubeconfig file (i.e. whatever cluster `kubectl cluster-info` shows). -`ModelRegistry` service needs a PostgreSQL or MySQL database. A sample Postgres configuration for testing is included +`ModelRegistry` service needs a PostgreSQL or MySQL database. A sample Postgres configuration for testing (without TLS security) is included in [postgres-db.yaml](config/samples/postgres/postgres-db.yaml) and a sample MySQL configuration for testing is included in [mysql-db.yaml](config/samples/mysql/mysql-db.yaml). To use another PostgreSQL instance, comment the line that includes the sample DB in [kustomization.yaml](config/samples/postgres/kustomization.yaml) and to use -your own mysql instance comment the line that includes the sample DB in [kustomization.yaml](config/samples/mysql/kustomization.yaml). +your own mysql instance comment the line that includes the sample DB in [kustomization.yaml](config/samples/mysql/kustomization.yaml). -### Running on the cluster -1. Install Instances of Custom Resources using one of the two database options: +The operator supports creating model registries using [Istio](https://istio.io/) for security including [Authorino](https://github.com/Kuadrant/authorino) for authorization. +It also supports [Istio Gateways](https://istio.io/latest/docs/concepts/traffic-management/#gateways) for exposing service endpoints for clients outside the Istio service mesh. -For MySQL use the command: +### Istio Configuration +_Skip this section if you are not using Istio._ -```sh -kubectl apply -k config/samples/mysql +*NOTE* that this section describes how to configure a couple of files in the kustomize samples in this project. However, there are only a handful of extra configuration properties needed in an Istio model registry. + +For using the Istio based samples, both Istio and Authorino MUST be installed before deploying the operator. +An Authorino instance MUST also be configured as a [custom authz](https://istio.io/latest/docs/tasks/security/authorization/authz-custom/) provider in the Istio control plane. + +Both Istio and Authorino along with the authz provider can be easily enabled in the Open Data Hub data science cluster instance. + +If Istio or Authorino is installed in the cluster after deploying this controller, restart the controller by deleting the pod `model-registry-operator-controller-manager` in `model-registry-operator-system` namespace. +If Model Registry component has been installed as an Open Data Hub operator component, the operator namespace will be `opendatahub`. + +If Authorino provider is from a non Open Data Hub cluster, configure its selector labels in the [authconfig-labels.yaml](config/samples/istio/components/authconfig-labels.yaml) file. + +To use the Istio model registry samples the following configuration data is needed in the [istio.env](config/samples/istio/components/istio.env) file: + +* AUTH_PROVIDER - name of the authorino external auth provider configured in the Istio control plane (defaults to `opendatahub-auth-provider` for Open Data Hub data science cluster with OpenShift Service Mesh enabled). +* DOMAIN - hostname domain suffix for gateway endpoints. +This depends upon your cluster's external load balancer config. In OpenShift clusters, it can be obtained with the command: +```shell +oc get ingresses.config/cluster -o jsonpath='{.spec.domain}' ``` -For PostgreSQL use the command: +* ISTIO_INGRESS - name of the Istio Ingress Gateway (defaults to `ingressgateway`). +* REST_CREDENTIAL_NAME - Kubernetes secret in IngressGateway namespace (typically `istio-system`) containing TLS certificates for REST service (defaults to `modelregistry-sample-rest-credential`). +* GRPC_CREDENTIAL_NAME - Kubernetes secret in IngressGateway namespace containing TLS certificates for gRPC service (defaults to `modelregistry-sample-grpc-credential`). + +### Running on the cluster +1. Deploy the controller to the cluster using the `latest` docker image: ```sh +make deploy +``` + +2. The operator includes multiple samples that use [kustomize](https://kustomize.io/) to create a sample model registry `modelregistry-sample`. + +* [MySQL without Istio](config/samples/mysql) plain Kubernetes model registry services with a sample MySQL database +* [PostgreSQL without Istio](config/samples/postgres) plain Kubernetes model registry services with a sample PostgreSQL database +* [MySQL with Istio](config/samples/istio/mysql) MySQL database, Istio, and plaintext Gateway +* [PostgreSQL with Istio](config/samples/istio/postgres) PostgreSQL database, Istio, and plaintext Gateway +* [MySQL with Istio and TLS](config/samples/istio/mysql-tls) MySQL database, Istio, and TLS Gateway endpoints +* [PostgreSQL with Istio and TLS](config/samples/istio/postgres-tls) PostgreSQL database, Istio, and TLS Gateway endpoints + +#### Istio Samples +**WARNING:** Istio samples without TLS are only meant for testing and demos to avoid having to create TLS certificates. They should only be used in local development clusters. + +##### Authorization +For all Istio samples, a Kubernetes user or serviceaccount authorization token MUST be passed in calls to model registry services using the header: + +```http request +Authorization: Bearer sha256~xxx +``` + +In OpenShift clusters, the user session token can be obtained using the command: + +```shell +oc whoami -t +``` + +##### TLS Certificates +The project [Makefile](Makefile) includes targets to manage test TLS certificates using a self signed CA certificate. +To create test certificates in the directory [certs](certs) and Kubernetes secrets in the `istio-system` namespace, use the command: + +```shell +make certificates +``` + +The test CA certificate is generated in the file [certs/domain.crt](certs/domain.crt) along with other certificates. See [generate_certs.sh](scripts/generate_certs.sh) for details. + +To cleanup the certificates and Kubernetes secrets, use the command: + +```shell +make certificates/clean +``` + +To disable Istio Gateway creation, create a kustomize overlay that removes the `gateway` yaml section in model registry custom resource or manually edit a sample yaml and it's corresponding `replacements.yaml` helper. + +##### Enable Namespace Istio Injection +If using upstream Istio (i.e. not OpenShift Service Mesh), enable Istio proxy injection in your test namespace by using the command: + +```shell +kubectl label namespace istio-injection=enabled --overwrite +``` + +If using OpenShift Service Mesh, enable it by adding the namespace to the control plane (e.g. ODH Istio control plane `data-science-smcp` below) by using the command: + +```shell +kubectl apply -f -</model-registry-operator:tag ``` -3. Deploy the controller to the cluster with the image specified by `IMG`: +2. Deploy the controller to the cluster with the image specified by `IMG`: ```sh make deploy IMG=/model-registry-operator:tag diff --git a/api/v1alpha1/modelregistry_types.go b/api/v1alpha1/modelregistry_types.go index 80021ed..ab0f75d 100644 --- a/api/v1alpha1/modelregistry_types.go +++ b/api/v1alpha1/modelregistry_types.go @@ -173,6 +173,113 @@ type GrpcSpec struct { Image string `json:"image,omitempty"` } +type TLSServerSettings struct { + + //+kubebuilder:default=SIMPLE + //+kubebuilder:validation:Enum=SIMPLE;MUTUAL;ISTIO_MUTUAL;OPTIONAL_MUTUAL + + // The value of this field determines how TLS is enforced. + // SIMPLE: Secure connections with standard TLS semantics. In this mode client certificate is not requested during handshake. + // + // MUTUAL: Secure connections to the downstream using mutual TLS by presenting server certificates for authentication. A client certificate will also be requested during the handshake and at least one valid certificate is required to be sent by the client. + // + // ISTIO_MUTUAL: Secure connections from the downstream using mutual TLS by presenting server certificates for authentication. Compared to Mutual mode, this mode uses certificates, representing gateway workload identity, generated automatically by Istio for mTLS authentication. When this mode is used, all other TLS fields should be empty. + // + // OPTIONAL_MUTUAL: Similar to MUTUAL mode, except that the client certificate is optional. Unlike SIMPLE mode, A client certificate will still be explicitly requested during handshake, but the client is not required to send a certificate. If a client certificate is presented, it will be validated. ca_certificates should be specified for validating client certificates. + Mode string `json:"mode"` + + // The name of the secret that holds the TLS certs including the CA certificates. + // An Opaque secret should contain the following + // keys and values: `tls.key: ` and `tls.crt: ` or + // `key: ` and `cert: `. + // For mutual TLS, `cacert: ` and `crl: ` + // can be provided in the same secret or a separate secret named `-cacert`. + // A TLS secret for server certificates with an additional `tls.ocsp-staple` key + // for specifying OCSP staple information, `ca.crt` key for CA certificates + // and `ca.crl` for certificate revocation list is also supported. + // Only one of server certificates and CA certificate + // or credentialName can be specified. + //+optional + CredentialName *string `json:"credentialName,omitempty"` +} + +type ServerConfig struct { + //+kubebuilder:validation:Minimum=0 + //+kubebuilder:validation:Maximum=65535 + + // Listen port for server connections, defaults to 80 without TLS and 443 when TLS settings are present. + Port *int32 `json:"port,omitempty"` + + // Set of TLS related options that govern the server's behavior. Use + // these options to control if all http requests should be redirected to + // https, and the TLS modes to use. + //+optional + TLS *TLSServerSettings `json:"tls,omitempty"` +} + +type GatewayConfig struct { + //+kubebuilder:required + + // Domain name for Gateway configuration + Domain string `json:"domain"` + + //+kubebuilder:default=ingressgateway + + // Value of label `istio` used to identify the Ingress Gateway + IstioIngress *string `json:"istioIngress,omitempty"` + + // Maistra/OpenShift Servicemesh control plane name + //+optional + ControlPlane *string `json:"controlPlane,omitempty"` + + // Rest gateway server config + Rest ServerConfig `json:"rest"` + + // gRPC gateway server config + Grpc ServerConfig `json:"grpc"` +} + +type IstioConfig struct { + //+kubebuilder:required + + // Authorino authentication provider name + AuthProvider string `json:"authProvider"` + + // Authorino AuthConfig selector labels + //+optional + AuthConfigLabels map[string]string `json:"authConfigLabels,omitempty"` + + //+kubebuilder:required + //+kubebuilder:default=ISTIO_MUTUAL + //+kubebuilder:Enum=DISABLE;SIMPLE;MUTUAL;ISTIO_MUTUAL + + // DestinationRule TLS mode. Defaults to ISTIO_MUTUAL. + // + // DISABLE: Do not setup a TLS connection to the upstream endpoint. + // + // SIMPLE: Originate a TLS connection to the upstream endpoint. + // + // MUTUAL: Secure connections to the upstream using mutual TLS by presenting + // client certificates for authentication. + // + // ISTIO_MUTUAL: Secure connections to the upstream using mutual TLS by presenting + // client certificates for authentication. + // Compared to Mutual mode, this mode uses certificates generated + // automatically by Istio for mTLS authentication. When this mode is + // used, all other fields in `ClientTLSSettings` should be empty. + TlsMode string `json:"tlsMode,omitempty"` + + // Optional Istio Gateway for registry services. + // Gateway is not created if set to null (default). + //+optional + Gateway *GatewayConfig `json:"gateway,omitempty"` + + // Optional Authorino AuthConfig credential audiences. This depends on the cluster identity provider. + // If not specified, operator will determine the cluster's audience using its own service account. + //+optional + Audiences []string `json:"audiences,omitempty"` +} + // ModelRegistrySpec defines the desired state of ModelRegistry. // One of `postgres` or `mysql` database configurations MUST be provided! type ModelRegistrySpec struct { @@ -184,7 +291,7 @@ type ModelRegistrySpec struct { // Configuration for REST endpoint Rest RestSpec `json:"rest"` - //+kubebuilder: required + //+kubebuilder:required // Configuration for gRPC endpoint Grpc GrpcSpec `json:"grpc"` @@ -207,6 +314,10 @@ type ModelRegistrySpec struct { // initialization (Optional Parameter) //+optional DowngradeDbSchemaVersion *int64 `json:"downgrade_db_schema_version,omitempty"` + + // Istio servicemesh configuration options + //+optional + Istio *IstioConfig `json:"istio,omitempty"` } // ModelRegistryStatus defines the observed state of ModelRegistry diff --git a/api/v1alpha1/modelregistry_webhook.go b/api/v1alpha1/modelregistry_webhook.go index 3f4def6..97dd22a 100644 --- a/api/v1alpha1/modelregistry_webhook.go +++ b/api/v1alpha1/modelregistry_webhook.go @@ -29,6 +29,14 @@ import ( // log is for logging in this package. var modelregistrylog = logf.Log.WithName("modelregistry-resource") +// default ports +const ( + DEFAULT_TLS_MODE = "ISTIO_MUTUAL" + DEFAULT_HTTP_PORT = 80 + DEFAULT_HTTPS_PORT = 443 + DEFAULT_ISTIO_GATEWAY = "ingressgateway" +) + func (r *ModelRegistry) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). @@ -39,13 +47,17 @@ func (r *ModelRegistry) SetupWebhookWithManager(mgr ctrl.Manager) error { //+kubebuilder:webhook:path=/mutate-modelregistry-opendatahub-io-v1alpha1-modelregistry,mutating=true,failurePolicy=fail,sideEffects=None,groups=modelregistry.opendatahub.io,resources=modelregistries,verbs=create;update,versions=v1alpha1,name=mmodelregistry.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &ModelRegistry{} +var ( + _ webhook.Defaulter = &ModelRegistry{} + gateway = DEFAULT_ISTIO_GATEWAY + httpPort int32 = DEFAULT_HTTP_PORT + httpsPort int32 = DEFAULT_HTTPS_PORT +) // Default implements webhook.Defaulter so a webhook will be registered for the type func (r *ModelRegistry) Default() { modelregistrylog.Info("default", "name", r.Name) - // TODO(user): fill in your defaulting logic. if r.Spec.Grpc.Resources == nil { r.Spec.Grpc.Resources = config.MlmdGRPCResourceRequirements.DeepCopy() } @@ -70,6 +82,35 @@ func (r *ModelRegistry) Default() { if r.Spec.MySQL != nil && len(r.Spec.MySQL.Host) == 0 { r.Spec.MySQL = nil } + + // istio defaults + if r.Spec.Istio != nil { + // set default TlsMode + if len(r.Spec.Istio.TlsMode) == 0 { + r.Spec.Istio.TlsMode = DEFAULT_TLS_MODE + } + if r.Spec.Istio.Gateway != nil { + // set ingress gateway if not set + if r.Spec.Istio.Gateway.IstioIngress == nil { + r.Spec.Istio.Gateway.IstioIngress = &gateway + } + // set default gateway ports if needed + if r.Spec.Istio.Gateway.Rest.Port == nil { + if r.Spec.Istio.Gateway.Rest.TLS != nil { + r.Spec.Istio.Gateway.Rest.Port = &httpsPort + } else { + r.Spec.Istio.Gateway.Rest.Port = &httpPort + } + } + if r.Spec.Istio.Gateway.Grpc.Port == nil { + if r.Spec.Istio.Gateway.Grpc.TLS != nil { + r.Spec.Istio.Gateway.Grpc.Port = &httpsPort + } else { + r.Spec.Istio.Gateway.Grpc.Port = &httpPort + } + } + } + } } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 4a07c02..8109d42 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -26,6 +26,33 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayConfig) DeepCopyInto(out *GatewayConfig) { + *out = *in + if in.IstioIngress != nil { + in, out := &in.IstioIngress, &out.IstioIngress + *out = new(string) + **out = **in + } + if in.ControlPlane != nil { + in, out := &in.ControlPlane, &out.ControlPlane + *out = new(string) + **out = **in + } + in.Rest.DeepCopyInto(&out.Rest) + in.Grpc.DeepCopyInto(&out.Grpc) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayConfig. +func (in *GatewayConfig) DeepCopy() *GatewayConfig { + if in == nil { + return nil + } + out := new(GatewayConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GrpcSpec) DeepCopyInto(out *GrpcSpec) { *out = *in @@ -51,6 +78,38 @@ func (in *GrpcSpec) DeepCopy() *GrpcSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IstioConfig) DeepCopyInto(out *IstioConfig) { + *out = *in + if in.AuthConfigLabels != nil { + in, out := &in.AuthConfigLabels, &out.AuthConfigLabels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Gateway != nil { + in, out := &in.Gateway, &out.Gateway + *out = new(GatewayConfig) + (*in).DeepCopyInto(*out) + } + if in.Audiences != nil { + in, out := &in.Audiences, &out.Audiences + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IstioConfig. +func (in *IstioConfig) DeepCopy() *IstioConfig { + if in == nil { + return nil + } + out := new(IstioConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ModelRegistry) DeepCopyInto(out *ModelRegistry) { *out = *in @@ -135,6 +194,11 @@ func (in *ModelRegistrySpec) DeepCopyInto(out *ModelRegistrySpec) { *out = new(int64) **out = **in } + if in.Istio != nil { + in, out := &in.Istio, &out.Istio + *out = new(IstioConfig) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModelRegistrySpec. @@ -308,3 +372,48 @@ func (in *SecretKeyValue) DeepCopy() *SecretKeyValue { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServerConfig) DeepCopyInto(out *ServerConfig) { + *out = *in + if in.Port != nil { + in, out := &in.Port, &out.Port + *out = new(int32) + **out = **in + } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(TLSServerSettings) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerConfig. +func (in *ServerConfig) DeepCopy() *ServerConfig { + if in == nil { + return nil + } + out := new(ServerConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSServerSettings) DeepCopyInto(out *TLSServerSettings) { + *out = *in + if in.CredentialName != nil { + in, out := &in.CredentialName, &out.CredentialName + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSServerSettings. +func (in *TLSServerSettings) DeepCopy() *TLSServerSettings { + if in == nil { + return nil + } + out := new(TLSServerSettings) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/main.go b/cmd/main.go index d7e21b1..9dd95ff 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -17,12 +17,16 @@ limitations under the License. package main import ( + "context" "flag" + authorino "github.com/kuadrant/authorino/api/v1beta2" + "github.com/opendatahub-io/model-registry-operator/internal/controller/config" + networking "istio.io/client-go/pkg/apis/networking/v1beta1" + security "istio.io/client-go/pkg/apis/security/v1beta1" + authentication "k8s.io/api/authentication/v1" "k8s.io/client-go/discovery" "os" - "github.com/opendatahub-io/model-registry-operator/internal/controller/config" - // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" @@ -50,7 +54,14 @@ const EnableWebhooks = "ENABLE_WEBHOOKS" func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + // openshift scheme utilruntime.Must(oapi.Install(scheme)) + // authorino scheme + utilruntime.Must(authorino.AddToScheme(scheme)) + // istio security scheme + utilruntime.Must(security.AddToScheme(scheme)) + // istio networking scheme + utilruntime.Must(networking.AddToScheme(scheme)) utilruntime.Must(modelregistryv1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme @@ -103,28 +114,52 @@ func main() { } setupLog.Info("parsed kubernetes templates", "templates", template.DefinedTemplates()) - discoveryClient := discovery.NewDiscoveryClientForConfigOrDie(mgr.GetConfig()) + mgrRestConfig := mgr.GetConfig() + client := mgr.GetClient() + tokenReview := &authentication.TokenReview{ + Spec: authentication.TokenReviewSpec{ + Token: mgrRestConfig.BearerToken, + }, + } + err = client.Create(context.Background(), tokenReview) + if err != nil { + setupLog.Error(err, "error getting controller serviceaccount audience") + os.Exit(1) + } + setupLog.Info("default authorino authconfig audiences", "audiences", tokenReview.Status.Audiences) + + discoveryClient := discovery.NewDiscoveryClientForConfigOrDie(mgrRestConfig) groups, err := discoveryClient.ServerGroups() if err != nil { setupLog.Error(err, "error discovering server groups") os.Exit(1) } isOpenShift := false + hasAuthorino := false + hasIstio := false for _, g := range groups.Groups { if g.Name == "route.openshift.io" { isOpenShift = true } + if g.Name == "authorino.kuadrant.io" { + hasAuthorino = true + } + if g.Name == "networking.istio.io" { + hasIstio = true + } } enableWebhooks := os.Getenv(EnableWebhooks) != "false" if err = (&controller.ModelRegistryReconciler{ - Client: mgr.GetClient(), + Client: client, Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorderFor("modelregistry-controller"), Log: ctrl.Log.WithName("controller"), Template: template, EnableWebhooks: enableWebhooks, IsOpenShift: isOpenShift, + HasIstio: hasAuthorino && hasIstio, + Audiences: tokenReview.Status.Audiences, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ModelRegistry") os.Exit(1) diff --git a/config/crd/bases/modelregistry.opendatahub.io_modelregistries.yaml b/config/crd/bases/modelregistry.opendatahub.io_modelregistries.yaml index 292479b..8c7584e 100644 --- a/config/crd/bases/modelregistry.opendatahub.io_modelregistries.yaml +++ b/config/crd/bases/modelregistry.opendatahub.io_modelregistries.yaml @@ -111,6 +111,195 @@ spec: type: object type: object type: object + istio: + description: Istio servicemesh configuration options + properties: + audiences: + description: Optional Authorino AuthConfig credential audiences. + This depends on the cluster identity provider. If not specified, + operator will determine the cluster's audience using its own + service account. + items: + type: string + type: array + authConfigLabels: + additionalProperties: + type: string + description: Authorino AuthConfig selector labels + type: object + authProvider: + description: Authorino authentication provider name + type: string + gateway: + description: Optional Istio Gateway for registry services. Gateway + is not created if set to null (default). + properties: + controlPlane: + description: Maistra/OpenShift Servicemesh control plane name + type: string + domain: + description: Domain name for Gateway configuration + type: string + grpc: + description: gRPC gateway server config + properties: + port: + description: Listen port for server connections, defaults + to 80 without TLS and 443 when TLS settings are present. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + tls: + description: Set of TLS related options that govern the + server's behavior. Use these options to control if all + http requests should be redirected to https, and the + TLS modes to use. + properties: + credentialName: + description: 'The name of the secret that holds the + TLS certs including the CA certificates. An Opaque + secret should contain the following keys and values: + `tls.key: ` and `tls.crt: ` + or `key: ` and `cert: `. + For mutual TLS, `cacert: ` and `crl: + ` can be provided in + the same secret or a separate secret named `-cacert`. + A TLS secret for server certificates with an additional + `tls.ocsp-staple` key for specifying OCSP staple + information, `ca.crt` key for CA certificates and + `ca.crl` for certificate revocation list is also + supported. Only one of server certificates and CA + certificate or credentialName can be specified.' + type: string + mode: + default: SIMPLE + description: "The value of this field determines how + TLS is enforced. SIMPLE: Secure connections with + standard TLS semantics. In this mode client certificate + is not requested during handshake. \n MUTUAL: Secure + connections to the downstream using mutual TLS by + presenting server certificates for authentication. + A client certificate will also be requested during + the handshake and at least one valid certificate + is required to be sent by the client. \n ISTIO_MUTUAL: + Secure connections from the downstream using mutual + TLS by presenting server certificates for authentication. + Compared to Mutual mode, this mode uses certificates, + representing gateway workload identity, generated + automatically by Istio for mTLS authentication. + When this mode is used, all other TLS fields should + be empty. \n OPTIONAL_MUTUAL: Similar to MUTUAL + mode, except that the client certificate is optional. + Unlike SIMPLE mode, A client certificate will still + be explicitly requested during handshake, but the + client is not required to send a certificate. If + a client certificate is presented, it will be validated. + ca_certificates should be specified for validating + client certificates." + enum: + - SIMPLE + - MUTUAL + - ISTIO_MUTUAL + - OPTIONAL_MUTUAL + type: string + required: + - mode + type: object + type: object + istioIngress: + default: ingressgateway + description: Value of label `istio` used to identify the Ingress + Gateway + type: string + rest: + description: Rest gateway server config + properties: + port: + description: Listen port for server connections, defaults + to 80 without TLS and 443 when TLS settings are present. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + tls: + description: Set of TLS related options that govern the + server's behavior. Use these options to control if all + http requests should be redirected to https, and the + TLS modes to use. + properties: + credentialName: + description: 'The name of the secret that holds the + TLS certs including the CA certificates. An Opaque + secret should contain the following keys and values: + `tls.key: ` and `tls.crt: ` + or `key: ` and `cert: `. + For mutual TLS, `cacert: ` and `crl: + ` can be provided in + the same secret or a separate secret named `-cacert`. + A TLS secret for server certificates with an additional + `tls.ocsp-staple` key for specifying OCSP staple + information, `ca.crt` key for CA certificates and + `ca.crl` for certificate revocation list is also + supported. Only one of server certificates and CA + certificate or credentialName can be specified.' + type: string + mode: + default: SIMPLE + description: "The value of this field determines how + TLS is enforced. SIMPLE: Secure connections with + standard TLS semantics. In this mode client certificate + is not requested during handshake. \n MUTUAL: Secure + connections to the downstream using mutual TLS by + presenting server certificates for authentication. + A client certificate will also be requested during + the handshake and at least one valid certificate + is required to be sent by the client. \n ISTIO_MUTUAL: + Secure connections from the downstream using mutual + TLS by presenting server certificates for authentication. + Compared to Mutual mode, this mode uses certificates, + representing gateway workload identity, generated + automatically by Istio for mTLS authentication. + When this mode is used, all other TLS fields should + be empty. \n OPTIONAL_MUTUAL: Similar to MUTUAL + mode, except that the client certificate is optional. + Unlike SIMPLE mode, A client certificate will still + be explicitly requested during handshake, but the + client is not required to send a certificate. If + a client certificate is presented, it will be validated. + ca_certificates should be specified for validating + client certificates." + enum: + - SIMPLE + - MUTUAL + - ISTIO_MUTUAL + - OPTIONAL_MUTUAL + type: string + required: + - mode + type: object + type: object + required: + - domain + - grpc + - rest + type: object + tlsMode: + default: ISTIO_MUTUAL + description: "DestinationRule TLS mode. Defaults to ISTIO_MUTUAL. + \n DISABLE: Do not setup a TLS connection to the upstream endpoint. + \n SIMPLE: Originate a TLS connection to the upstream endpoint. + \n MUTUAL: Secure connections to the upstream using mutual TLS + by presenting client certificates for authentication. \n ISTIO_MUTUAL: + Secure connections to the upstream using mutual TLS by presenting + client certificates for authentication. Compared to Mutual mode, + this mode uses certificates generated automatically by Istio + for mTLS authentication. When this mode is used, all other fields + in `ClientTLSSettings` should be empty." + type: string + required: + - authProvider + type: object mysql: description: MySQL configuration options properties: diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 030df52..134357b 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -5,7 +5,7 @@ resources: - bases/modelregistry.opendatahub.io_modelregistries.yaml #+kubebuilder:scaffold:crdkustomizeresource -patches: +#patches: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD #- path: patches/webhook_in_modelregistries.yaml diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 3b78fb3..17a4115 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -16,6 +16,18 @@ rules: - patch - update - watch +- apiGroups: + - authorino.kuadrant.io + resources: + - authconfigs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - "" resources: @@ -70,6 +82,66 @@ rules: - get - patch - update +- apiGroups: + - networking.istio.io + resources: + - destinationrules + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - networking.istio.io + resources: + - gateways + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - networking.istio.io + resources: + - virtualservices + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - roles + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - route.openshift.io resources: @@ -82,3 +154,27 @@ rules: - patch - update - watch +- apiGroups: + - security.istio.io + resources: + - authorizationpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - user.openshift.io + resources: + - groups + verbs: + - create + - delete + - get + - list + - patch + - update + - watch diff --git a/config/samples/istio/components/authconfig-labels.yaml b/config/samples/istio/components/authconfig-labels.yaml new file mode 100644 index 0000000..8fea57d --- /dev/null +++ b/config/samples/istio/components/authconfig-labels.yaml @@ -0,0 +1,4 @@ +- op: replace + path: /spec/istio/authConfigLabels + value: + security.opendatahub.io/authorization-group: default diff --git a/config/samples/istio/components/istio.env b/config/samples/istio/components/istio.env new file mode 100644 index 0000000..6008f81 --- /dev/null +++ b/config/samples/istio/components/istio.env @@ -0,0 +1,5 @@ +AUTH_PROVIDER=opendatahub-auth-provider +DOMAIN=my-domain +ISTIO_INGRESS=ingressgateway +REST_CREDENTIAL_NAME=modelregistry-sample-rest-credential +GRPC_CREDENTIAL_NAME=modelregistry-sample-grpc-credential diff --git a/config/samples/istio/components/istio_modelregistry.yaml b/config/samples/istio/components/istio_modelregistry.yaml new file mode 100644 index 0000000..f6d7784 --- /dev/null +++ b/config/samples/istio/components/istio_modelregistry.yaml @@ -0,0 +1,13 @@ +apiVersion: modelregistry.opendatahub.io/v1alpha1 +kind: ModelRegistry +metadata: + name: modelregistry-sample +spec: + istio: + authProvider: AUTH_PROVIDER + authConfigLabels: {} + gateway: + domain: DOMAIN + istioIngress: ISTIO_INGRESS + rest: {} + grpc: {} diff --git a/config/samples/istio/components/kustomization.yaml b/config/samples/istio/components/kustomization.yaml new file mode 100644 index 0000000..0480731 --- /dev/null +++ b/config/samples/istio/components/kustomization.yaml @@ -0,0 +1,26 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +# Istio config patch +patches: + - path: istio_modelregistry.yaml + - target: + group: modelregistry.opendatahub.io + version: v1alpha1 + kind: ModelRegistry + name: modelregistry-sample + path: authconfig-labels.yaml + +# Config map and replacements to use istio.env for cluster specific config +configMapGenerator: + - envs: + - istio.env + files: + - authconfig-labels.yaml + name: modelregistry-sample-environment +generatorOptions: + disableNameSuffixHash: true +replacements: + - path: replacements.yaml + +#+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/istio/components/replacements.yaml b/config/samples/istio/components/replacements.yaml new file mode 100644 index 0000000..6afad82 --- /dev/null +++ b/config/samples/istio/components/replacements.yaml @@ -0,0 +1,32 @@ +# replacements from configmap using istio.env for cluster specific config +- source: + kind: ConfigMap + name: modelregistry-sample-environment + fieldPath: data.AUTH_PROVIDER + targets: + - select: + apiVersion: modelregistry.opendatahub.io/v1alpha1 + kind: ModelRegistry + name: modelregistry-sample + fieldPaths: + - spec.istio.authProvider +- source: + kind: ConfigMap + name: modelregistry-sample-environment + fieldPath: data.DOMAIN + targets: + - select: + kind: ModelRegistry + name: modelregistry-sample + fieldPaths: + - spec.istio.gateway.domain +- source: + kind: ConfigMap + name: modelregistry-sample-environment + fieldPath: data.ISTIO_INGRESS + targets: + - select: + kind: ModelRegistry + name: modelregistry-sample + fieldPaths: + - spec.istio.gateway.istioIngress diff --git a/config/samples/istio/components/tls/istio_tls_modelregistry.yaml b/config/samples/istio/components/tls/istio_tls_modelregistry.yaml new file mode 100644 index 0000000..e1f80fb --- /dev/null +++ b/config/samples/istio/components/tls/istio_tls_modelregistry.yaml @@ -0,0 +1,13 @@ +apiVersion: modelregistry.opendatahub.io/v1alpha1 +kind: ModelRegistry +metadata: + name: modelregistry-sample +spec: + istio: + gateway: + rest: + tls: + credentialName: REST_CREDENTIAL_NAME + grpc: + tls: + credentialName: GRPC_CREDENTIAL_NAME diff --git a/config/samples/istio/components/tls/kustomization.yaml b/config/samples/istio/components/tls/kustomization.yaml new file mode 100644 index 0000000..ff53383 --- /dev/null +++ b/config/samples/istio/components/tls/kustomization.yaml @@ -0,0 +1,12 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +## Append samples of your project ## + +# Istio tls config patch +patches: + - path: istio_tls_modelregistry.yaml + +replacements: + - path: tls-replacements.yaml + +#+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/istio/components/tls/tls-replacements.yaml b/config/samples/istio/components/tls/tls-replacements.yaml new file mode 100644 index 0000000..12f490e --- /dev/null +++ b/config/samples/istio/components/tls/tls-replacements.yaml @@ -0,0 +1,21 @@ +# replacements from configmap using istio.env for cluster specific config +- source: + kind: ConfigMap + name: modelregistry-sample-environment + fieldPath: data.REST_CREDENTIAL_NAME + targets: + - select: + kind: ModelRegistry + name: modelregistry-sample + fieldPaths: + - spec.istio.gateway.rest.tls.credentialName +- source: + kind: ConfigMap + name: modelregistry-sample-environment + fieldPath: data.GRPC_CREDENTIAL_NAME + targets: + - select: + kind: ModelRegistry + name: modelregistry-sample + fieldPaths: + - spec.istio.gateway.grpc.tls.credentialName diff --git a/config/samples/istio/mysql-tls/kustomization.yaml b/config/samples/istio/mysql-tls/kustomization.yaml new file mode 100644 index 0000000..1536bd9 --- /dev/null +++ b/config/samples/istio/mysql-tls/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +## Append samples of your project ## +resources: + - ../mysql + +components: + - ../components/tls + +#+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/istio/mysql/kustomization.yaml b/config/samples/istio/mysql/kustomization.yaml new file mode 100644 index 0000000..eaa1000 --- /dev/null +++ b/config/samples/istio/mysql/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +## Append samples of your project ## +resources: +- ../../mysql + +components: +- ../components + +#+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/istio/postgres-tls/kustomization.yaml b/config/samples/istio/postgres-tls/kustomization.yaml new file mode 100644 index 0000000..4f97e9c --- /dev/null +++ b/config/samples/istio/postgres-tls/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +## Append samples of your project ## +resources: + - ../postgres + +components: + - ../components/tls + +#+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/istio/postgres/kustomization.yaml b/config/samples/istio/postgres/kustomization.yaml new file mode 100644 index 0000000..8f773cc --- /dev/null +++ b/config/samples/istio/postgres/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +## Append samples of your project ## +resources: + - ../../postgres + +components: + - ../components + +#+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/mysql/mysql-db.yaml b/config/samples/mysql/mysql-db.yaml index 3fda2ef..65e60b1 100644 --- a/config/samples/mysql/mysql-db.yaml +++ b/config/samples/mysql/mysql-db.yaml @@ -12,6 +12,7 @@ items: nodePort: 0 port: 3306 protocol: TCP + appProtocol: tcp targetPort: 3306 selector: name: model-registry-db @@ -43,10 +44,9 @@ items: type: Recreate template: metadata: - annotations: - sidecar.istio.io/inject: "false" labels: name: model-registry-db + sidecar.istio.io/inject: "false" spec: containers: - env: diff --git a/config/samples/postgres/postgres-db.yaml b/config/samples/postgres/postgres-db.yaml index 813d631..191cf9e 100644 --- a/config/samples/postgres/postgres-db.yaml +++ b/config/samples/postgres/postgres-db.yaml @@ -12,6 +12,7 @@ items: nodePort: 0 port: 5432 protocol: TCP + appProtocol: tcp targetPort: 5432 selector: name: model-registry-db @@ -43,10 +44,9 @@ items: type: Recreate template: metadata: - annotations: - sidecar.istio.io/inject: "false" labels: name: model-registry-db + sidecar.istio.io/inject: "false" spec: containers: - env: diff --git a/go.mod b/go.mod index 91112dd..047271e 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,12 @@ go 1.20 require ( github.com/banzaicloud/k8s-objectmatcher v1.8.0 github.com/go-logr/logr v1.4.1 + github.com/kuadrant/authorino v0.17.1 github.com/onsi/ginkgo/v2 v2.16.0 github.com/onsi/gomega v1.31.1 github.com/openshift/api v0.0.0-20231116201359-a5824a0c15b6 github.com/spf13/viper v1.18.2 + istio.io/client-go v1.20.3-0.20240116015948-bc198a253520 k8s.io/api v0.28.3 k8s.io/apimachinery v0.28.3 k8s.io/client-go v0.28.3 @@ -61,6 +63,9 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/tidwall/gjson v1.14.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.25.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect @@ -73,17 +78,20 @@ require ( golang.org/x/tools v0.17.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + istio.io/api v1.20.3-0.20240116015448-5563f7225778 // indirect k8s.io/apiextensions-apiserver v0.28.3 // indirect k8s.io/component-base v0.28.3 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index eda902e..f3dd6b6 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -128,6 +129,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kuadrant/authorino v0.17.1 h1:NXcYLDGSpokDE5VwzqWuRI07ChUsRNVKJB85uzOf35k= +github.com/kuadrant/authorino v0.17.1/go.mod h1:al71fN0FX6c9Orrhk9GR4CtjtC+CD/lUHJCs7drlRNM= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -205,6 +208,12 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w= +github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -305,6 +314,10 @@ google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -341,8 +354,13 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +istio.io/api v1.20.3-0.20240116015448-5563f7225778 h1:F+6gDkT2g1uPIVhu8HIykfKJrdQxJdCRNIvlsHRHXD4= +istio.io/api v1.20.3-0.20240116015448-5563f7225778/go.mod h1:hm1PE/mGdIAsjCDkTIAplP53H7TjO5LUQCiVvF26SVg= +istio.io/client-go v1.20.3-0.20240116015948-bc198a253520 h1:hNbJvMXPz8FmVvNpnW17c08lQW5Ar/KHey8AH9YD1VA= +istio.io/client-go v1.20.3-0.20240116015948-bc198a253520/go.mod h1:Og8R24St0xkuVaY99aAeuVJ1s8agpOIju/bOEoU2/8A= k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM= k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc= k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= @@ -369,8 +387,8 @@ sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnro sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= +sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= diff --git a/internal/controller/config/defaults.go b/internal/controller/config/defaults.go index 7194a1d..be349f2 100644 --- a/internal/controller/config/defaults.go +++ b/internal/controller/config/defaults.go @@ -25,6 +25,7 @@ import ( ) //go:embed templates/*.yaml.tmpl +//go:embed templates/istio/*.yaml.tmpl var templateFS embed.FS const ( @@ -68,7 +69,7 @@ func GetStringConfigWithDefault(configName, value string) string { } func ParseTemplates() (*template.Template, error) { - template, err := template.ParseFS(templateFS, "templates/*.yaml.tmpl") + template, err := template.ParseFS(templateFS, "templates/*.yaml.tmpl", "templates/istio/*.yaml.tmpl") if err != nil { return nil, err } diff --git a/internal/controller/config/defaults_test.go b/internal/controller/config/defaults_test.go index e700880..cc61658 100644 --- a/internal/controller/config/defaults_test.go +++ b/internal/controller/config/defaults_test.go @@ -23,3 +23,44 @@ func TestGetStringConfigWithDefault(t *testing.T) { }) } } + +/*func TestParseTemplates(t *testing.T) { + tests := []struct { + name string + spec v1alpha1.ModelRegistrySpec + want string + wantErr bool + }{ + {name: "role.yaml.tmpl"}, + } + + // parse all templates + templates, err := ParseTemplates() + if err != nil { + t.Errorf("ParseTemplates() error = %v", err) + } + reconciler := controller.ModelRegistryReconciler{ + Log: logr.Logger{}, + Template: templates, + IsOpenShift: true, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + params := controller.ModelRegistryParams{ + Name: "test", + Namespace: "test-namespace", + Spec: tt.spec, + } + got, err := reconciler.Apply(params, tt.name, result) + if (err != nil) != tt.wantErr { + t.Errorf("ParseTemplates() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseTemplates() got = %v, want %v", got, tt.want) + } + }) + } +} +*/ diff --git a/internal/controller/config/templates/deployment.yaml.tmpl b/internal/controller/config/templates/deployment.yaml.tmpl index 05c3de9..41195df 100644 --- a/internal/controller/config/templates/deployment.yaml.tmpl +++ b/internal/controller/config/templates/deployment.yaml.tmpl @@ -6,6 +6,12 @@ metadata: labels: app: {{.Name}} component: model-registry + app.kubernetes.io/name: {{.Name}} + app.kubernetes.io/instance: {{.Name}} + app.kubernetes.io/component: model-registry + app.kubernetes.io/created-by: model-registry-operator + app.kubernetes.io/part-of: model-registry + app.kubernetes.io/managed-by: model-registry-operator spec: replicas: 1 revisionHistoryLimit: 0 @@ -18,6 +24,13 @@ spec: labels: app: {{.Name}} component: model-registry + sidecar.istio.io/inject: "true" + annotations: + {{- if .Spec.Postgres}} + traffic.sidecar.istio.io/excludeOutboundPorts: {{.Spec.Postgres.Port}} + {{- else if .Spec.MySQL}} + traffic.sidecar.istio.io/excludeOutboundPorts: {{.Spec.MySQL.Port}} + {{- end}} spec: containers: - args: @@ -162,7 +175,7 @@ spec: port: grpc-api timeoutSeconds: 2 readinessProbe: - initialDelaySeconds: 3 + initialDelaySeconds: 15 periodSeconds: 5 tcpSocket: port: grpc-api @@ -198,7 +211,7 @@ spec: port: http-api timeoutSeconds: 2 readinessProbe: - initialDelaySeconds: 3 + initialDelaySeconds: 15 periodSeconds: 5 tcpSocket: port: http-api diff --git a/internal/controller/config/templates/group.yaml.tmpl b/internal/controller/config/templates/group.yaml.tmpl new file mode 100644 index 0000000..dbb7df6 --- /dev/null +++ b/internal/controller/config/templates/group.yaml.tmpl @@ -0,0 +1,13 @@ +apiVersion: user.openshift.io/v1 +kind: Group +metadata: + name: {{.Name}}-users + labels: + app: {{.Name}} + component: model-registry + app.kubernetes.io/name: {{.Name}} + app.kubernetes.io/instance: {{.Name}}-users + app.kubernetes.io/component: model-registry + app.kubernetes.io/created-by: model-registry-operator + app.kubernetes.io/part-of: model-registry + app.kubernetes.io/managed-by: model-registry-operator diff --git a/internal/controller/config/templates/http-route.yaml.tmpl b/internal/controller/config/templates/http-route.yaml.tmpl index 79d83fa..8f663e1 100644 --- a/internal/controller/config/templates/http-route.yaml.tmpl +++ b/internal/controller/config/templates/http-route.yaml.tmpl @@ -6,6 +6,12 @@ metadata: labels: app: {{.Name}} component: model-registry + app.kubernetes.io/name: {{.Name}} + app.kubernetes.io/instance: {{.Name}}-http + app.kubernetes.io/component: model-registry + app.kubernetes.io/created-by: model-registry-operator + app.kubernetes.io/part-of: model-registry + app.kubernetes.io/managed-by: model-registry-operator spec: to: kind: Service diff --git a/internal/controller/config/templates/istio/authconfig.yaml.tmpl b/internal/controller/config/templates/istio/authconfig.yaml.tmpl new file mode 100644 index 0000000..e9bdf83 --- /dev/null +++ b/internal/controller/config/templates/istio/authconfig.yaml.tmpl @@ -0,0 +1,59 @@ +apiVersion: authorino.kuadrant.io/v1beta2 +kind: AuthConfig +metadata: + name: {{.Name}} + namespace: {{.Namespace}} + labels: + {{- with .Spec.Istio.AuthConfigLabels}} + {{- range $key, $value := .}} + {{$key}}: {{$value}} + {{- end}} + {{- end}} + app: {{.Name}} + component: model-registry + app.kubernetes.io/name: {{.Name}} + app.kubernetes.io/instance: {{.Name}} + app.kubernetes.io/component: model-registry + app.kubernetes.io/created-by: model-registry-operator + app.kubernetes.io/part-of: model-registry + app.kubernetes.io/managed-by: model-registry-operator +spec: + authentication: + cluster-users: + credentials: + authorizationHeader: { } + kubernetesTokenReview: + audiences: + {{- with .Spec.Istio.Audiences}} + {{- range .}} + - {{.}} + {{- end}} + {{- else}} + - https://kubernetes.default.svc + {{- end}} + authorization: + k8s-rbac: + kubernetesSubjectAccessReview: + user: + selector: auth.identity.metadata.annotations.userid + groups: + - {{.Name}}-users + resourceAttributes: + verb: + value: get + group: + value: "" + resource: + value: services + namespace: + value: {{.Namespace}} + name: + value: {{.Name}} + hosts: + {{- if and .Spec.Istio.Gateway .Spec.Istio.Gateway.Domain}} + - {{.Name}}-rest.{{.Spec.Istio.Gateway.Domain}} + - {{.Name}}-grpc.{{.Spec.Istio.Gateway.Domain}} + {{- end}} + - {{.Name}}.{{.Namespace}}.svc.cluster.local + - {{.Name}}.{{.Namespace}} + - {{.Name}} diff --git a/internal/controller/config/templates/istio/authorino-authorization-policy.yaml.tmpl b/internal/controller/config/templates/istio/authorino-authorization-policy.yaml.tmpl new file mode 100644 index 0000000..5d2ef1d --- /dev/null +++ b/internal/controller/config/templates/istio/authorino-authorization-policy.yaml.tmpl @@ -0,0 +1,24 @@ +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: {{.Name}}-authorino + namespace: {{.Namespace}} + labels: + app: {{.Name}} + component: model-registry + app.kubernetes.io/name: {{.Name}} + app.kubernetes.io/instance: {{.Name}}-authorino + app.kubernetes.io/component: model-registry + app.kubernetes.io/created-by: model-registry-operator + app.kubernetes.io/part-of: model-registry + app.kubernetes.io/managed-by: model-registry-operator +spec: + action: CUSTOM + selector: + matchLabels: + app: {{.Name}} + component: model-registry + provider: + name: {{.Spec.Istio.AuthProvider}} + rules: + - {} diff --git a/internal/controller/config/templates/istio/destination-rule.yaml.tmpl b/internal/controller/config/templates/istio/destination-rule.yaml.tmpl new file mode 100644 index 0000000..b337df0 --- /dev/null +++ b/internal/controller/config/templates/istio/destination-rule.yaml.tmpl @@ -0,0 +1,19 @@ +apiVersion: networking.istio.io/v1beta1 +kind: DestinationRule +metadata: + name: {{.Name}} + namespace: {{.Namespace}} + labels: + app: {{.Name}} + component: model-registry + app.kubernetes.io/name: {{.Name}} + app.kubernetes.io/instance: {{.Name}} + app.kubernetes.io/component: model-registry + app.kubernetes.io/created-by: model-registry-operator + app.kubernetes.io/part-of: model-registry + app.kubernetes.io/managed-by: model-registry-operator +spec: + host: {{.Name}} + trafficPolicy: + tls: + mode: {{.Spec.Istio.TlsMode}} diff --git a/internal/controller/config/templates/istio/gateway.yaml.tmpl b/internal/controller/config/templates/istio/gateway.yaml.tmpl new file mode 100644 index 0000000..97749c5 --- /dev/null +++ b/internal/controller/config/templates/istio/gateway.yaml.tmpl @@ -0,0 +1,49 @@ +apiVersion: networking.istio.io/v1beta1 +kind: Gateway +metadata: + name: {{.Name}} + namespace: {{.Namespace}} + annotations: + maistra.io/manageRoute: "true" + labels: + app: {{.Name}} + component: model-registry + app.kubernetes.io/name: {{.Name}} + app.kubernetes.io/instance: {{.Name}} + app.kubernetes.io/component: model-registry + app.kubernetes.io/created-by: model-registry-operator + app.kubernetes.io/part-of: model-registry + app.kubernetes.io/managed-by: model-registry-operator +spec: + # The selector matches the ingress gateway pod labels. + # If you installed Istio using Helm following the standard documentation, this would be "istio=ingress" + # ingressgateway for istio default controller + selector: + istio: {{.Spec.Istio.Gateway.IstioIngress}} + {{- with .Spec.Istio.Gateway.ControlPlane}} + maistra.io/owner-name: {{.}} + {{- end}} + servers: + - port: + number: {{.Spec.Istio.Gateway.Rest.Port}} + name: rest + protocol: {{with .Spec.Istio.Gateway.Rest.TLS}}HTTPS{{- else}}HTTP{{- end}} + hosts: + - {{.Name}}-rest.{{.Spec.Istio.Gateway.Domain}} + name: {{.Name}}-rest-server + {{- with .Spec.Istio.Gateway.Rest.TLS}}{{- template "server-tls-settings" .}}{{- end}} + - port: + number: {{.Spec.Istio.Gateway.Grpc.Port}} + name: grpc + protocol: {{with .Spec.Istio.Gateway.Grpc.TLS}}HTTPS{{- else}}GRPC{{- end}} + hosts: + - {{.Name}}-grpc.{{.Spec.Istio.Gateway.Domain}} + name: {{.Name}}-grpc-server + {{- with .Spec.Istio.Gateway.Grpc.TLS}}{{- template "server-tls-settings" .}}{{- end}} +{{- define "server-tls-settings"}} + tls: + mode: {{.Mode}} + {{- with .CredentialName}} + credentialName: {{.}} + {{- end}} +{{- end}} \ No newline at end of file diff --git a/internal/controller/config/templates/istio/virtual-service.yaml.tmpl b/internal/controller/config/templates/istio/virtual-service.yaml.tmpl new file mode 100644 index 0000000..6ed424d --- /dev/null +++ b/internal/controller/config/templates/istio/virtual-service.yaml.tmpl @@ -0,0 +1,62 @@ +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: {{.Name}} + namespace: {{.Namespace}} + labels: + app: {{.Name}} + component: model-registry + app.kubernetes.io/name: {{.Name}} + app.kubernetes.io/instance: {{.Name}} + app.kubernetes.io/component: model-registry + app.kubernetes.io/created-by: model-registry-operator + app.kubernetes.io/part-of: model-registry + app.kubernetes.io/managed-by: model-registry-operator +spec: + gateways: + - {{.Name}} + hosts: + - {{.Name}} + {{- if and .Spec.Istio.Gateway .Spec.Istio.Gateway.Domain}} + - {{.Name}}-rest.{{.Spec.Istio.Gateway.Domain}} + - {{.Name}}-grpc.{{.Spec.Istio.Gateway.Domain}} + {{- end}} + http: + - match: + - headers: + :authority: + regex: {{.Name}}(\..*)?:{{.Spec.Rest.Port}} + route: + - destination: + host: {{.Name}} + port: + number: {{.Spec.Rest.Port}} + - match: + - headers: + :authority: + regex: {{.Name}}(\..*)?:{{.Spec.Grpc.Port}} + route: + - destination: + host: {{.Name}} + port: + number: {{.Spec.Grpc.Port}} + {{- if and .Spec.Istio.Gateway .Spec.Istio.Gateway.Domain}} + - match: + - headers: + :authority: + regex: {{.Name}}-rest.{{.Spec.Istio.Gateway.Domain}}(:{{.Spec.Istio.Gateway.Rest.Port}})? + route: + - destination: + host: {{.Name}} + port: + number: {{.Spec.Rest.Port}} + - match: + - headers: + :authority: + regex: {{.Name}}-grpc.{{.Spec.Istio.Gateway.Domain}}(:{{.Spec.Istio.Gateway.Grpc.Port}})? + route: + - destination: + host: {{.Name}} + port: + number: {{.Spec.Grpc.Port}} + {{- end}} diff --git a/internal/controller/config/templates/role-binding.yaml.tmpl b/internal/controller/config/templates/role-binding.yaml.tmpl new file mode 100644 index 0000000..e5f914b --- /dev/null +++ b/internal/controller/config/templates/role-binding.yaml.tmpl @@ -0,0 +1,22 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{.Name}}-users + namespace: {{.Namespace}} + labels: + app: {{.Name}} + component: model-registry + app.kubernetes.io/name: {{.Name}} + app.kubernetes.io/instance: {{.Name}}-users + app.kubernetes.io/component: model-registry + app.kubernetes.io/created-by: model-registry-operator + app.kubernetes.io/part-of: model-registry + app.kubernetes.io/managed-by: model-registry-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: registry-user-{{.Name}} +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: {{.Name}}-users diff --git a/internal/controller/config/templates/role.yaml.tmpl b/internal/controller/config/templates/role.yaml.tmpl new file mode 100644 index 0000000..9029ce0 --- /dev/null +++ b/internal/controller/config/templates/role.yaml.tmpl @@ -0,0 +1,26 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: registry-user-{{.Name}} + namespace: {{.Namespace}} + labels: + app: {{.Name}} + component: model-registry + app.kubernetes.io/name: {{.Name}} + app.kubernetes.io/instance: registry-user-{{.Name}} + app.kubernetes.io/component: model-registry + app.kubernetes.io/created-by: model-registry-operator + app.kubernetes.io/part-of: model-registry + app.kubernetes.io/managed-by: model-registry-operator + annotations: + openshift.io/display-name: Registry User {{.Name}} + openshift.io/description: Can access Model Registry {{.Name}} +rules: +- apiGroups: + - "" + resourceNames: + - {{.Name}} + resources: + - services + verbs: + - get diff --git a/internal/controller/config/templates/service.yaml.tmpl b/internal/controller/config/templates/service.yaml.tmpl index 8cdf133..8b9db5e 100644 --- a/internal/controller/config/templates/service.yaml.tmpl +++ b/internal/controller/config/templates/service.yaml.tmpl @@ -6,14 +6,22 @@ metadata: labels: app: {{.Name}} component: model-registry + app.kubernetes.io/name: {{.Name}} + app.kubernetes.io/instance: {{.Name}} + app.kubernetes.io/component: model-registry + app.kubernetes.io/created-by: model-registry-operator + app.kubernetes.io/part-of: model-registry + app.kubernetes.io/managed-by: model-registry-operator spec: ports: - name: grpc-api port: {{.Spec.Grpc.Port}} protocol: TCP + appProtocol: grpc - name: http-api port: {{.Spec.Rest.Port}} protocol: TCP + appProtocol: http selector: app: {{.Name}} component: model-registry diff --git a/internal/controller/config/templates/serviceaccount.yaml.tmpl b/internal/controller/config/templates/serviceaccount.yaml.tmpl index 32cd1f4..fca98d1 100644 --- a/internal/controller/config/templates/serviceaccount.yaml.tmpl +++ b/internal/controller/config/templates/serviceaccount.yaml.tmpl @@ -6,3 +6,9 @@ metadata: labels: app: {{.Name}} component: model-registry + app.kubernetes.io/name: {{.Name}} + app.kubernetes.io/instance: {{.Name}} + app.kubernetes.io/component: model-registry + app.kubernetes.io/created-by: model-registry-operator + app.kubernetes.io/part-of: model-registry + app.kubernetes.io/managed-by: model-registry-operator diff --git a/internal/controller/modelregistry_controller.go b/internal/controller/modelregistry_controller.go index e293bfe..a35a295 100644 --- a/internal/controller/modelregistry_controller.go +++ b/internal/controller/modelregistry_controller.go @@ -18,14 +18,20 @@ package controller import ( "context" + errors2 "errors" "fmt" "github.com/banzaicloud/k8s-objectmatcher/patch" "github.com/go-logr/logr" + authorino "github.com/kuadrant/authorino/api/v1beta2" modelregistryv1alpha1 "github.com/opendatahub-io/model-registry-operator/api/v1alpha1" "github.com/opendatahub-io/model-registry-operator/internal/controller/config" routev1 "github.com/openshift/api/route/v1" + userv1 "github.com/openshift/api/user/v1" + networking "istio.io/client-go/pkg/apis/networking/v1beta1" + security "istio.io/client-go/pkg/apis/security/v1beta1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + rbac "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -67,6 +73,8 @@ type ModelRegistryReconciler struct { Template *template.Template EnableWebhooks bool IsOpenShift bool + HasIstio bool + Audiences []string } // Reconcile is part of the main kubernetes reconciliation loop which aims to @@ -118,14 +126,15 @@ func (r *ModelRegistryReconciler) Reconcile(ctx context.Context, req ctrl.Reques if controllerutil.ContainsFinalizer(modelRegistry, modelRegistryFinalizer) { log.Info("Performing Finalizer Operations for modelRegistry before delete CR") - // Let's add here an status "Degraded" to define that this resource begin its process to be terminated. + // Let's add status "Degraded" to define that this resource has begun its process to be terminated. meta.SetStatusCondition(&modelRegistry.Status.Conditions, metav1.Condition{Type: ConditionTypeDegraded, Status: metav1.ConditionUnknown, Reason: "Finalizing", Message: fmt.Sprintf("Performing finalizer operations for the custom resource: %s ", modelRegistry.Name)}) if err = r.Status().Update(ctx, modelRegistry); IgnoreDeletingErrors(err) != nil { - switch t := err.(type) { - case *errors.StatusError: + var t *errors.StatusError + switch { + case errors2.As(err, &t): log.Error(err, "status error", "status", t.Status()) } log.Error(err, "Failed to update modelRegistry status") @@ -141,7 +150,7 @@ func (r *ModelRegistryReconciler) Reconcile(ctx context.Context, req ctrl.Reques // otherwise, you should requeue here. // Re-fetch the modelRegistry Custom Resource before update the status - // so that we have the latest state of the resource on the cluster and we will avoid + // so that we have the latest state of the resource on the cluster, and we will avoid // raise the issue "the object has been modified, please apply // your changes to the latest version and try again" which would re-trigger the reconciliation if err = r.Get(ctx, req.NamespacedName, modelRegistry); IgnoreDeletingErrors(err) != nil { @@ -232,17 +241,33 @@ func (r *ModelRegistryReconciler) SetupWithManager(mgr ctrl.Manager) error { if r.IsOpenShift { builder = builder.Owns(&routev1.Route{}) } + if r.HasIstio { + builder = builder.Owns(&authorino.AuthConfig{}). + Owns(&security.AuthorizationPolicy{}). + Owns(&networking.DestinationRule{}). + Owns(&networking.Gateway{}). + Owns(&networking.VirtualService{}) + } return builder.Complete(r) } -//+kubebuilder:rbac:groups=modelregistry.opendatahub.io,resources=modelregistries,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=modelregistry.opendatahub.io,resources=modelregistries/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=modelregistry.opendatahub.io,resources=modelregistries/finalizers,verbs=update -//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch -//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=services;serviceaccounts,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=route.openshift.io,resources=routes,verbs=get;list;watch;create;update;patch;delete +// NOTE: There MUST be an empty newline at the end of this rbac permissions list, or role generation won't work!!! +// +kubebuilder:rbac:groups=modelregistry.opendatahub.io,resources=modelregistries,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=modelregistry.opendatahub.io,resources=modelregistries/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=modelregistry.opendatahub.io,resources=modelregistries/finalizers,verbs=update +// +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch +// +kubebuilder:rbac:groups=core,resources=services;serviceaccounts,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=route.openshift.io,resources=routes,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=user.openshift.io,resources=groups,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=authorino.kuadrant.io,resources=authconfigs,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=security.istio.io,resources=authorizationpolicies,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=networking.istio.io,resources=destinationrules,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=networking.istio.io,resources=gateways,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=networking.istio.io,resources=virtualservices,verbs=get;list;watch;create;update;patch;delete func (r *ModelRegistryReconciler) updateRegistryResources(ctx context.Context, params *ModelRegistryParams, registry *modelregistryv1alpha1.ModelRegistry) (OperationResult, error) { var result, result2 OperationResult @@ -261,7 +286,41 @@ func (r *ModelRegistryReconciler) updateRegistryResources(ctx context.Context, p result = result2 } + result2, err = r.createOrUpdateDeployment(ctx, params, registry, "deployment.yaml.tmpl") + if err != nil { + return result2, err + } + if result2 != ResourceUnchanged { + result = result2 + } + + result2, err = r.createOrUpdateRole(ctx, params, registry, "role.yaml.tmpl") + if err != nil { + return result2, err + } + if result2 != ResourceUnchanged { + result = result2 + } + if r.IsOpenShift { + // create default group and role binding in OpenShift cluster + result2, err = r.createOrUpdateGroup(ctx, params, registry, "group.yaml.tmpl") + if err != nil { + return result2, err + } + if result2 != ResourceUnchanged { + result = result2 + } + + result2, err = r.createOrUpdateRoleBinding(ctx, params, registry, "role-binding.yaml.tmpl") + if err != nil { + return result2, err + } + if result2 != ResourceUnchanged { + result = result2 + } + + // create simple openshift service route, if configured result2, err = r.createOrUpdateRoute(ctx, params, registry, "http-route.yaml.tmpl") if err != nil { return result2, err @@ -271,12 +330,14 @@ func (r *ModelRegistryReconciler) updateRegistryResources(ctx context.Context, p } } - result2, err = r.createOrUpdateDeployment(ctx, params, registry, "deployment.yaml.tmpl") - if err != nil { - return result2, err - } - if result2 != ResourceUnchanged { - result = result2 + if registry.Spec.Istio != nil { + result2, err = r.createOrUpdateIstioConfig(ctx, params, registry) + if err != nil { + return result2, err + } + if result2 != ResourceUnchanged { + result = result2 + } } return result, nil @@ -303,6 +364,8 @@ func (r *ModelRegistryReconciler) setRegistryStatus(ctx context.Context, req ctr status = metav1.ConditionFalse reason = ReasonUpdating message = "Updating deployment for custom resource %s" + case ResourceUnchanged: + // ignore } meta.SetStatusCondition(&modelRegistry.Status.Conditions, metav1.Condition{Type: ConditionTypeProgressing, @@ -346,6 +409,198 @@ func (r *ModelRegistryReconciler) setRegistryStatus(ctx context.Context, req ctr return nil } +func (r *ModelRegistryReconciler) createOrUpdateIstioConfig(ctx context.Context, params *ModelRegistryParams, registry *modelregistryv1alpha1.ModelRegistry) (OperationResult, error) { + var result, result2 OperationResult + + // are AuthConfig audiences specified? + if len(params.Spec.Istio.Audiences) == 0 { + // use operator serviceaccount audiences by default + params.Spec.Istio.Audiences = r.Audiences + registry.Spec.Istio.Audiences = r.Audiences + } + + var err error + result, err = r.createOrUpdateVirtualService(ctx, params, registry, "virtual-service.yaml.tmpl") + if err != nil { + return result, err + } + + result2, err = r.createOrUpdateDestinationRule(ctx, params, registry, "destination-rule.yaml.tmpl") + if err != nil { + return result2, err + } + if result2 != ResourceUnchanged { + result = result2 + } + + result2, err = r.createOrUpdateAuthorizationPolicy(ctx, params, registry, "authorino-authorization-policy.yaml.tmpl") + if err != nil { + return result2, err + } + if result2 != ResourceUnchanged { + result = result2 + } + + result2, err = r.createOrUpdateAuthConfig(ctx, params, registry, "authconfig.yaml.tmpl") + if err != nil { + return result2, err + } + if result2 != ResourceUnchanged { + result = result2 + } + + result2, err = r.createOrUpdateGateway(ctx, params, registry, "gateway.yaml.tmpl") + if err != nil { + return result2, err + } + if result2 != ResourceUnchanged { + result = result2 + } + + return result, nil +} + +func (r *ModelRegistryReconciler) createOrUpdateGateway(ctx context.Context, params *ModelRegistryParams, + registry *modelregistryv1alpha1.ModelRegistry, templateName string) (result OperationResult, err error) { + result = ResourceUnchanged + var gateway networking.Gateway + if err = r.Apply(params, templateName, &gateway); err != nil { + return result, err + } + if err = ctrl.SetControllerReference(registry, &gateway, r.Scheme); err != nil { + return result, err + } + + result, err = r.createOrUpdate(ctx, gateway.DeepCopy(), &gateway) + if err != nil { + return result, err + } + return result, nil +} + +func (r *ModelRegistryReconciler) createOrUpdateAuthConfig(ctx context.Context, params *ModelRegistryParams, + registry *modelregistryv1alpha1.ModelRegistry, templateName string) (result OperationResult, err error) { + result = ResourceUnchanged + var authConfig authorino.AuthConfig + if err = r.Apply(params, templateName, &authConfig); err != nil { + return result, err + } + if err = ctrl.SetControllerReference(registry, &authConfig, r.Scheme); err != nil { + return result, err + } + + result, err = r.createOrUpdate(ctx, authConfig.DeepCopy(), &authConfig) + if err != nil { + return result, err + } + return result, nil +} + +func (r *ModelRegistryReconciler) createOrUpdateAuthorizationPolicy(ctx context.Context, params *ModelRegistryParams, + registry *modelregistryv1alpha1.ModelRegistry, templateName string) (result OperationResult, err error) { + result = ResourceUnchanged + var authorizationPolicy security.AuthorizationPolicy + if err = r.Apply(params, templateName, &authorizationPolicy); err != nil { + return result, err + } + if err = ctrl.SetControllerReference(registry, &authorizationPolicy, r.Scheme); err != nil { + return result, err + } + + result, err = r.createOrUpdate(ctx, authorizationPolicy.DeepCopy(), &authorizationPolicy) + if err != nil { + return result, err + } + return result, nil +} + +func (r *ModelRegistryReconciler) createOrUpdateDestinationRule(ctx context.Context, params *ModelRegistryParams, + registry *modelregistryv1alpha1.ModelRegistry, templateName string) (result OperationResult, err error) { + result = ResourceUnchanged + var destinationRule networking.DestinationRule + if err = r.Apply(params, templateName, &destinationRule); err != nil { + return result, err + } + if err = ctrl.SetControllerReference(registry, &destinationRule, r.Scheme); err != nil { + return result, err + } + + result, err = r.createOrUpdate(ctx, destinationRule.DeepCopy(), &destinationRule) + if err != nil { + return result, err + } + return result, nil +} + +func (r *ModelRegistryReconciler) createOrUpdateVirtualService(ctx context.Context, params *ModelRegistryParams, + registry *modelregistryv1alpha1.ModelRegistry, templateName string) (result OperationResult, err error) { + result = ResourceUnchanged + var virtualService networking.VirtualService + if err = r.Apply(params, templateName, &virtualService); err != nil { + return result, err + } + if err = ctrl.SetControllerReference(registry, &virtualService, r.Scheme); err != nil { + return result, err + } + + result, err = r.createOrUpdate(ctx, virtualService.DeepCopy(), &virtualService) + if err != nil { + return result, err + } + return result, nil +} + +func (r *ModelRegistryReconciler) createOrUpdateRoleBinding(ctx context.Context, params *ModelRegistryParams, + registry *modelregistryv1alpha1.ModelRegistry, templateName string) (result OperationResult, err error) { + result = ResourceUnchanged + var roleBinding rbac.RoleBinding + if err = r.Apply(params, templateName, &roleBinding); err != nil { + return result, err + } + if err = ctrl.SetControllerReference(registry, &roleBinding, r.Scheme); err != nil { + return result, err + } + + result, err = r.createOrUpdate(ctx, roleBinding.DeepCopy(), &roleBinding) + if err != nil { + return result, err + } + return result, nil +} + +func (r *ModelRegistryReconciler) createOrUpdateRole(ctx context.Context, params *ModelRegistryParams, + registry *modelregistryv1alpha1.ModelRegistry, templateName string) (result OperationResult, err error) { + result = ResourceUnchanged + var role rbac.Role + if err = r.Apply(params, templateName, &role); err != nil { + return result, err + } + if err = ctrl.SetControllerReference(registry, &role, r.Scheme); err != nil { + return result, err + } + + result, err = r.createOrUpdate(ctx, role.DeepCopy(), &role) + if err != nil { + return result, err + } + return result, nil +} + +func (r *ModelRegistryReconciler) createOrUpdateGroup(ctx context.Context, params *ModelRegistryParams, + _ *modelregistryv1alpha1.ModelRegistry, templateName string) (result OperationResult, err error) { + result = ResourceUnchanged + var group userv1.Group + if err = r.Apply(params, templateName, &group); err != nil { + return result, err + } + + result, err = r.createOrUpdate(ctx, group.DeepCopy(), &group) + if err != nil { + return result, err + } + return result, nil +} + func (r *ModelRegistryReconciler) createOrUpdateDeployment(ctx context.Context, params *ModelRegistryParams, registry *modelregistryv1alpha1.ModelRegistry, templateName string) (result OperationResult, err error) { result = ResourceUnchanged @@ -473,6 +728,10 @@ func (r *ModelRegistryReconciler) createOrUpdate(ctx context.Context, currObj cl if err := patch.DefaultAnnotator.SetLastAppliedAnnotation(newObj); err != nil { return result, err } + // set metadata.resourceVersion if present + if len(currObj.GetResourceVersion()) != 0 { + newObj.SetResourceVersion(currObj.GetResourceVersion()) + } return result, r.Client.Update(ctx, newObj) } @@ -499,14 +758,14 @@ func (r *ModelRegistryReconciler) doFinalizerOperationsForModelRegistry(registry registry.Namespace)) } -// wrapper for template parameters +// ModelRegistryParams is a wrapper for template parameters type ModelRegistryParams struct { Name string Namespace string Spec modelregistryv1alpha1.ModelRegistrySpec } -// executes given template name with params +// Apply executes given template name with params func (r *ModelRegistryReconciler) Apply(params *ModelRegistryParams, templateName string, object interface{}) error { builder := strings.Builder{} err := r.Template.ExecuteTemplate(&builder, templateName, params) @@ -532,5 +791,7 @@ func (r *ModelRegistryReconciler) logResultAsEvent(registry *modelregistryv1alph fmt.Sprintf("Updated service for custom resource %s in namespace %s", registry.Name, registry.Namespace)) + case ResourceUnchanged: + // ignore } } diff --git a/scripts/generate_certs.sh b/scripts/generate_certs.sh new file mode 100755 index 0000000..2023900 --- /dev/null +++ b/scripts/generate_certs.sh @@ -0,0 +1,12 @@ +DOMAIN=$1 +mkdir -p certs +# create CA cert +openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj "/O=modelregistry Inc./CN=$DOMAIN" -keyout certs/domain.key -out certs/domain.crt +# create rest cert and private key +echo "subjectAltName = DNS:modelregistry-sample-rest.$DOMAIN" > certs/modelregistry-sample-rest.domain.ext +openssl req -out certs/modelregistry-sample-rest.domain.csr -newkey rsa:2048 -nodes -keyout certs/modelregistry-sample-rest.domain.key -subj "/CN=modelregistry-sample-rest/O=modelregistry organization" -addext "subjectAltName = DNS:modelregistry-sample-rest.$DOMAIN" +openssl x509 -req -sha256 -days 365 -CA certs/domain.crt -CAkey certs/domain.key -set_serial 0 -in certs/modelregistry-sample-rest.domain.csr -out certs/modelregistry-sample-rest.domain.crt -extfile certs/modelregistry-sample-rest.domain.ext +# create grpc cert and private key +echo "subjectAltName = DNS:modelregistry-sample-grpc.$DOMAIN" > certs/modelregistry-sample-grpc.domain.ext +openssl req -out certs/modelregistry-sample-grpc.domain.csr -newkey rsa:2048 -nodes -keyout certs/modelregistry-sample-grpc.domain.key -subj "/CN=modelregistry-sample-grpc/O=modelregistry organization" -addext "subjectAltName = DNS:modelregistry-sample-grpc.$DOMAIN" +openssl x509 -req -sha256 -days 365 -CA certs/domain.crt -CAkey certs/domain.key -set_serial 0 -in certs/modelregistry-sample-grpc.domain.csr -out certs/modelregistry-sample-grpc.domain.crt -extfile certs/modelregistry-sample-grpc.domain.ext