diff --git a/bundle/manifests/rhods-operator.clusterserviceversion.yaml b/bundle/manifests/rhods-operator.clusterserviceversion.yaml index 87aab1c33b5..15d26a4cac8 100644 --- a/bundle/manifests/rhods-operator.clusterserviceversion.yaml +++ b/bundle/manifests/rhods-operator.clusterserviceversion.yaml @@ -99,7 +99,7 @@ metadata: categories: AI/Machine Learning, Big Data certified: "False" containerImage: quay.io/opendatahub/opendatahub-operator:v2.0.0 - createdAt: "2024-07-24T19:29:46Z" + createdAt: "2024-10-15T10:03:01Z" features.operators.openshift.io/disconnected: "true" features.operators.openshift.io/fips-compliant: "false" features.operators.openshift.io/proxy-aware: "false" @@ -169,7 +169,7 @@ metadata: operators.operatorframework.io/builder: operator-sdk-v1.31.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 repository: https://github.com/red-hat-data-services/rhods-operator - name: rhods-operator.v2.12.0 + name: rhods-operator.v2.13.1 namespace: placeholder spec: apiservicedefinitions: {} @@ -518,6 +518,14 @@ spec: verbs: - create - patch + - apiGroups: + - config.openshift.io + resources: + - authentications + verbs: + - get + - list + - watch - apiGroups: - config.openshift.io resources: @@ -1778,4 +1786,4 @@ spec: minKubeVersion: 1.25.0 provider: name: Red Hat - version: 2.12.0 + version: 2.13.1 diff --git a/config/crd/external/config.openshift.io_authentications.yaml b/config/crd/external/config.openshift.io_authentications.yaml new file mode 100644 index 00000000000..5755ad090c7 --- /dev/null +++ b/config/crd/external/config.openshift.io_authentications.yaml @@ -0,0 +1,175 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.1 + name: authentications.config.openshift.io +spec: + group: config.openshift.io + names: + kind: Authentication + listKind: AuthenticationList + plural: authentications + singular: authentication + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + Authentication specifies cluster-wide settings for authentication (like OAuth and + webhook token authenticators). The canonical name of an instance is `cluster`. + + Compatibility level 1: Stable within a major release for a minimum of 12 months or 3 minor releases (whichever is longer). + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec holds user settable values for configuration + properties: + oauthMetadata: + description: |- + oauthMetadata contains the discovery endpoint data for OAuth 2.0 + Authorization Server Metadata for an external OAuth server. + This discovery document can be viewed from its served location: + oc get --raw '/.well-known/oauth-authorization-server' + For further details, see the IETF Draft: + https://tools.ietf.org/html/draft-ietf-oauth-discovery-04#section-2 + If oauthMetadata.name is non-empty, this value has precedence + over any metadata reference stored in status. + The key "oauthMetadata" is used to locate the data. + If specified and the config map or expected key is not found, no metadata is served. + If the specified metadata is not valid, no metadata is served. + The namespace for this config map is openshift-config. + properties: + name: + description: name is the metadata.name of the referenced config + map + type: string + required: + - name + type: object + serviceAccountIssuer: + description: |- + serviceAccountIssuer is the identifier of the bound service account token + issuer. + The default is https://kubernetes.default.svc + WARNING: Updating this field will not result in immediate invalidation of all bound tokens with the + previous issuer value. Instead, the tokens issued by previous service account issuer will continue to + be trusted for a time period chosen by the platform (currently set to 24h). + This time period is subject to change over time. + This allows internal components to transition to use new service account issuer without service distruption. + type: string + type: + description: |- + type identifies the cluster managed, user facing authentication mode in use. + Specifically, it manages the component that responds to login attempts. + The default is IntegratedOAuth. + type: string + webhookTokenAuthenticator: + description: |- + webhookTokenAuthenticator configures a remote token reviewer. + These remote authentication webhooks can be used to verify bearer tokens + via the tokenreviews.authentication.k8s.io REST API. This is required to + honor bearer tokens that are provisioned by an external authentication service. + properties: + kubeConfig: + description: |- + kubeConfig references a secret that contains kube config file data which + describes how to access the remote webhook service. + The namespace for the referenced secret is openshift-config. + + For further details, see: + + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication + + The key "kubeConfig" is used to locate the data. + If the secret or expected key is not found, the webhook is not honored. + If the specified kube config data is not valid, the webhook is not honored. + properties: + name: + description: name is the metadata.name of the referenced secret + type: string + required: + - name + type: object + required: + - kubeConfig + type: object + webhookTokenAuthenticators: + description: webhookTokenAuthenticators is DEPRECATED, setting it + has no effect. + items: + description: |- + deprecatedWebhookTokenAuthenticator holds the necessary configuration options for a remote token authenticator. + It's the same as WebhookTokenAuthenticator but it's missing the 'required' validation on KubeConfig field. + properties: + kubeConfig: + description: |- + kubeConfig contains kube config file data which describes how to access the remote webhook service. + For further details, see: + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication + The key "kubeConfig" is used to locate the data. + If the secret or expected key is not found, the webhook is not honored. + If the specified kube config data is not valid, the webhook is not honored. + The namespace for this secret is determined by the point of use. + properties: + name: + description: name is the metadata.name of the referenced + secret + type: string + required: + - name + type: object + type: object + type: array + type: object + status: + description: status holds observed values from the cluster. They may not + be overridden. + properties: + integratedOAuthMetadata: + description: |- + integratedOAuthMetadata contains the discovery endpoint data for OAuth 2.0 + Authorization Server Metadata for the in-cluster integrated OAuth server. + This discovery document can be viewed from its served location: + oc get --raw '/.well-known/oauth-authorization-server' + For further details, see the IETF Draft: + https://tools.ietf.org/html/draft-ietf-oauth-discovery-04#section-2 + This contains the observed value based on cluster state. + An explicitly set value in spec.oauthMetadata has precedence over this field. + This field has no meaning if authentication spec.type is not set to IntegratedOAuth. + The key "oauthMetadata" is used to locate the data. + If the config map or expected key is not found, no metadata is served. + If the specified metadata is not valid, no metadata is served. + The namespace for this config map is openshift-config-managed. + properties: + name: + description: name is the metadata.name of the referenced config + map + type: string + required: + - name + type: object + type: object + required: + - spec + type: object + served: true + storage: true \ No newline at end of file diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index c219b847dea..8eb3cbeaa96 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -271,6 +271,14 @@ rules: verbs: - create - patch +- apiGroups: + - config.openshift.io + resources: + - authentications + verbs: + - get + - list + - watch - apiGroups: - config.openshift.io resources: diff --git a/controllers/dscinitialization/dscinitialization_controller.go b/controllers/dscinitialization/dscinitialization_controller.go index 7ca934682a5..2d7cfc6e833 100644 --- a/controllers/dscinitialization/dscinitialization_controller.go +++ b/controllers/dscinitialization/dscinitialization_controller.go @@ -31,6 +31,7 @@ import ( corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" + k8serr "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" @@ -75,6 +76,7 @@ type DSCInitializationReconciler struct { // +kubebuilder:rbac:groups="dscinitialization.opendatahub.io",resources=dscinitializations,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups="features.opendatahub.io",resources=featuretrackers,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups="features.opendatahub.io",resources=featuretrackers/status,verbs=get;update;patch;delete +// +kubebuilder:rbac:groups="config.openshift.io",resources=authentications,verbs=get;watch;list // Reconcile contains controller logic specific to DSCInitialization instance updates. func (r *DSCInitializationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { //nolint:funlen,gocyclo,maintidx @@ -224,11 +226,20 @@ func (r *DSCInitializationReconciler) Reconcile(ctx context.Context, req ctrl.Re } return ctrl.Result{}, nil default: + createUsergroup, err := cluster.IsDefaultAuthMethod(ctx, r.Client) + if err != nil && !k8serr.IsNotFound(err) { // only keep reconcile if real error but not missing CRD or missing CR + return ctrl.Result{}, err + } switch platform { case cluster.SelfManagedRhods: - err := r.createUserGroup(ctx, instance, "rhods-admins") - if err != nil { - return reconcile.Result{}, err + // Check if user opted for disabling creating user groups + if !createUsergroup { + r.Log.Info("DSCI disabled usergroup creation") + } else { + err := r.createUserGroup(ctx, instance, "rhods-admins") + if err != nil { + return reconcile.Result{}, err + } } if instance.Spec.Monitoring.ManagementState == operatorv1.Managed { r.Log.Info("Monitoring enabled, won't apply changes", "cluster", "Self-Managed RHODS Mode") @@ -257,9 +268,14 @@ func (r *DSCInitializationReconciler) Reconcile(ctx context.Context, req ctrl.Re } } default: - err := r.createUserGroup(ctx, instance, "odh-admins") - if err != nil { - return reconcile.Result{}, err + // Check if user opted for disabling creating user groups + if !createUsergroup { + r.Log.Info("DSCI disabled usergroup creation") + } else { + err := r.createUserGroup(ctx, instance, "odh-admins") + if err != nil { + return reconcile.Result{}, err + } } if instance.Spec.Monitoring.ManagementState == operatorv1.Managed { r.Log.Info("Monitoring enabled, won't apply changes", "cluster", "ODH Mode") diff --git a/controllers/dscinitialization/dscinitialization_test.go b/controllers/dscinitialization/dscinitialization_test.go index d558f1e916d..e45902d4878 100644 --- a/controllers/dscinitialization/dscinitialization_test.go +++ b/controllers/dscinitialization/dscinitialization_test.go @@ -4,6 +4,7 @@ import ( "context" operatorv1 "github.com/openshift/api/operator/v1" + userv1 "github.com/openshift/api/user/v1" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -21,6 +22,7 @@ import ( const ( workingNamespace = "test-operator-ns" applicationNamespace = "test-application-ns" + usergroupName = "odh-admins" configmapName = "odh-common-config" applicationName = "default-dsci" monitoringNamespace = "test-monitoring-ns" @@ -119,6 +121,15 @@ var _ = Describe("DataScienceCluster initialization", func() { Expect(foundConfigMap.Data).To(Equal(expectedConfigmapData)) }) + It("Should not create user group when we do not have authentications CR in the cluster", func(ctx context.Context) { + userGroup := &userv1.Group{} + Eventually(objectExists(usergroupName, "", userGroup)). + WithContext(ctx). + WithTimeout(timeout). + WithPolling(interval). + Should(BeFalse()) + }) + }) Context("Monitoring Resource", func() { @@ -368,7 +379,7 @@ func namespaceExists(ns string, obj client.Object) func(ctx context.Context) boo } } -func objectExists(ns string, name string, obj client.Object) func(ctx context.Context) bool { //nolint:unparam +func objectExists(ns string, name string, obj client.Object) func(ctx context.Context) bool { return func(ctx context.Context) bool { err := k8sClient.Get(ctx, client.ObjectKey{Name: ns, Namespace: name}, obj) diff --git a/controllers/dscinitialization/suite_test.go b/controllers/dscinitialization/suite_test.go index f3ef428f878..985618bacf8 100644 --- a/controllers/dscinitialization/suite_test.go +++ b/controllers/dscinitialization/suite_test.go @@ -22,6 +22,7 @@ import ( "testing" "time" + configv1 "github.com/openshift/api/config/v1" routev1 "github.com/openshift/api/route/v1" userv1 "github.com/openshift/api/user/v1" ofapi "github.com/operator-framework/api/pkg/operators/v1alpha1" @@ -116,6 +117,7 @@ var _ = BeforeSuite(func() { utilruntime.Must(routev1.Install(testScheme)) utilruntime.Must(userv1.Install(testScheme)) utilruntime.Must(monitoringv1.AddToScheme(testScheme)) + utilruntime.Must(configv1.Install(testScheme)) // +kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: testScheme}) diff --git a/pkg/cluster/cluster_config.go b/pkg/cluster/cluster_config.go index a1c462aeeee..b723dc4c13a 100644 --- a/pkg/cluster/cluster_config.go +++ b/pkg/cluster/cluster_config.go @@ -8,10 +8,12 @@ import ( "strings" "github.com/blang/semver/v4" + configv1 "github.com/openshift/api/config/v1" "github.com/operator-framework/api/pkg/lib/version" ofapiv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" corev1 "k8s.io/api/core/v1" k8serr "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" @@ -169,3 +171,19 @@ func GetRelease(ctx context.Context, cli client.Client) (Release, error) { initRelease.Version = csv.Spec.Version return initRelease, nil } + +// IsDefaultAuthMethod returns true if the default authentication method is IntegratedOAuth or empty. +// This will give indication that Operator should create userGroups or not in the cluster. +func IsDefaultAuthMethod(ctx context.Context, cli client.Client) (bool, error) { + authenticationobj := &configv1.Authentication{} + if err := cli.Get(ctx, client.ObjectKey{Name: ClusterAuthenticationObj, Namespace: ""}, authenticationobj); err != nil { + if errors.Is(err, &meta.NoKindMatchError{}) { // when CRD is missing, conver error type + return false, k8serr.NewNotFound(configv1.Resource("authentications"), ClusterAuthenticationObj) + } + return false, err + } + // for now, HPC support "" "None" "IntegratedOAuth"(default) "OIDC" + // other offering support "" "None" "IntegratedOAuth"(default) + // we only create userGroups for "IntegratedOAuth" or "" and leave other or new supported type value in the future + return authenticationobj.Spec.Type == configv1.AuthenticationTypeIntegratedOAuth || authenticationobj.Spec.Type == "", nil +} diff --git a/pkg/cluster/const.go b/pkg/cluster/const.go index 4b47a03bd5a..21deb6d2110 100644 --- a/pkg/cluster/const.go +++ b/pkg/cluster/const.go @@ -9,4 +9,6 @@ const ( OpenDataHub Platform = "Open Data Hub" // Unknown indicates that operator is not deployed using OLM. Unknown Platform = "" + // Default cluster-scope Authentication CR name. + ClusterAuthenticationObj = "cluster" )