├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── DEVELOPMENT.md ├── Dockerfile ├── LICENSE ├── Makefile ├── OWNERS ├── PROJECT ├── README.md ├── apis ├── certmanager │ ├── v1 │ │ ├── certificate_types.go │ │ ├── const.go │ │ ├── groupversion_info.go │ │ ├── types.go │ │ └── zz_generated.deepcopy.go │ └── v1alpha1 │ │ ├── certificate_common.go │ │ ├── certificate_types.go │ │ ├── groupversion_info.go │ │ └── zz_generated.deepcopy.go ├── oidc.security │ └── v1 │ │ ├── client_types.go │ │ ├── groupversion_info.go │ │ └── zz_generated.deepcopy.go ├── operator │ └── v1alpha1 │ │ ├── authentication_types.go │ │ ├── groupversion_info.go │ │ ├── operandbindinfo_types.go │ │ ├── operandrequest_types.go │ │ └── zz_generated.deepcopy.go └── zen.cpd.ibm.com │ └── v1 │ ├── groupversion_info.go │ ├── zenextension_types.go │ └── zz_generated.deepcopy.go ├── base_images.json ├── bundle.Dockerfile ├── bundle ├── manifests │ ├── ibm-iam-operator.clusterserviceversion.yaml │ ├── oidc.security.ibm.com_clients.yaml │ └── operator.ibm.com_authentications.yaml ├── metadata │ └── annotations.yaml └── tests │ └── scorecard │ └── config.yaml ├── common ├── Makefile.common.mk ├── config │ ├── .golangci.yml │ ├── .hadolint.yml │ ├── .yamllint.yml │ ├── mdl.rb │ ├── sass-lint.yml │ └── tslint.json ├── manifest.yaml └── scripts │ ├── .githooks │ ├── make_lint-all.sh │ └── pre-commit │ ├── config_docker.sh │ ├── gobuild.sh │ ├── install-operator-sdk.sh │ ├── lint-csv.sh │ ├── lint_copyright_banner.sh │ ├── lint_go.sh │ └── multiarch_image.sh ├── config ├── crd │ ├── bases │ │ ├── oidc.security.ibm.com_clients.yaml │ │ └── operator.ibm.com_authentications.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_oidc.security_clients.yaml │ │ ├── cainjection_in_operator_authentications.yaml │ │ ├── webhook_in_oidc.security_clients.yaml │ │ └── webhook_in_operator_authentications.yaml ├── default │ ├── manager_auth_proxy_patch.yaml │ ├── manager_config_patch.yaml │ └── overlays │ │ └── prod │ │ └── kustomization.yaml ├── manager │ ├── bases │ │ ├── kustomization.yaml │ │ └── manager.yaml │ ├── kustomization.yaml │ └── overlays │ │ └── prod │ │ ├── image_env_vars_patch.yaml │ │ └── kustomization.yaml ├── manifests │ ├── bases │ │ ├── ibm-iam-operator.clusterserviceversion.yaml │ │ └── kustomization.yaml │ ├── kustomization.yaml │ └── overlays │ │ └── prod │ │ ├── annotations_patch.yaml │ │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── oidc.security_client_editor_role.yaml │ ├── oidc.security_client_viewer_role.yaml │ ├── operator_authentication_editor_role.yaml │ ├── operator_authentication_viewer_role.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml ├── samples │ ├── bases │ │ ├── kustomization.yaml │ │ ├── oidc.security_v1_client.yaml │ │ └── operator_v1alpha1_authentication.yaml │ ├── kustomization.yaml │ └── overlays │ │ └── prod │ │ ├── authentication_image_patch.yaml │ │ └── kustomization.yaml └── scorecard │ ├── bases │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ ├── basic.config.yaml │ └── olm.config.yaml ├── controllers ├── bootstrap │ └── authentication_bootstrap_controller.go ├── common │ ├── constants.go │ ├── randomstring.go │ ├── resources.go │ ├── secondary.go │ ├── shatag.go │ ├── suite_test.go │ ├── utils.go │ └── utils_test.go ├── oidc.security │ ├── client_controller.go │ ├── client_controller_config.go │ ├── clientreg.go │ ├── conditions.go │ ├── errors.go │ ├── iam.go │ ├── suite_test.go │ └── zen_registration.go └── operator │ ├── authentication_controller.go │ ├── authentication_controller_test.go │ ├── certificate.go │ ├── certificate_test.go │ ├── clusterrole.go │ ├── clusterrolebinding.go │ ├── configmap.go │ ├── configmap_test.go │ ├── constants.go │ ├── containers.go │ ├── deployment.go │ ├── deployment_test.go │ ├── hpa.go │ ├── ingress.go │ ├── job.go │ ├── matcher.go │ ├── migration.go │ ├── operandbindinfo.go │ ├── operandbindinfo_test.go │ ├── operandrequest.go │ ├── operandrequest_test.go │ ├── resourcestatus.go │ ├── role.go │ ├── rolebinding.go │ ├── routes.go │ ├── routes_test.go │ ├── sacc.go │ ├── secret.go │ ├── service.go │ ├── suite_test.go │ ├── testdata │ └── crds │ │ ├── certmanager.k8s.io_certificates_v1.yaml │ │ ├── certmanager.k8s.io_certificates_v1alpha1.yaml │ │ ├── odlm │ │ └── operandbindinfo_crd.yaml │ │ ├── routes │ │ └── route_crd.yaml │ │ └── zen │ │ └── zenextension_crd.yaml │ └── zenextension.go ├── database ├── connectors │ ├── dbconn.go │ └── options.go ├── migrate.go ├── migration │ ├── migration.go │ └── migration_test.go └── schema │ └── v1 │ ├── dbinit.sql │ ├── migration_test.go │ ├── migrations.go │ ├── mongotoedb.go │ ├── suite_test.go │ ├── tables.go │ ├── tables_test.go │ └── version.go ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── bundle-render ├── channel-render ├── create_dev_overlays ├── external-crs.json ├── is_semver ├── manager_patch.yaml ├── patch-built-bundle ├── relatedimages.yaml ├── update_image_refs └── update_operator_version ├── helm-cluster-scoped ├── Chart.yaml └── templates │ ├── 00_operator.ibm.com_authentications.yaml │ ├── 01_oidc.security.ibm.com_clients.yaml │ ├── 10_clusterrole.yaml │ └── 11_clusterrolebinding.yaml ├── helm ├── Chart.yaml ├── templates │ ├── 00-rbac.yaml │ └── 10-deployment.yaml └── values.yaml ├── main.go ├── renovate.json ├── testing └── utils.go └── version └── version.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin 8 | 9 | # Test binary, build with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Kubernetes Generated files - skip generated files, except for vendored files 16 | 17 | !vendor/**/zz_generated.* 18 | 19 | # editor and IDE paraphernalia 20 | .idea 21 | *.swp 22 | *.swo 23 | *~ 24 | .vscode 25 | 26 | cache 27 | build/_output/* 28 | config/**/overlays/dev 29 | bundle-dev 30 | bundle-dev.Dockerfile 31 | catalog_output 32 | catalog 33 | catalog.Dockerfile 34 | licenses 35 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # 14 | 15 | FROM docker-na-public.artifactory.swg-devops.com/hyc-cloud-private-edge-docker-local/build-images/ubi9-minimal:latest 16 | 17 | ARG VCS_REF 18 | ARG VCS_URL 19 | ARG IMG_VERSION 20 | ARG IMG_RELEASE 21 | 22 | LABEL org.label-schema.vendor="IBM" \ 23 | org.label-schema.name="ibm-iam-operator" \ 24 | org.label-schema.description="IBM IAM Operator" \ 25 | org.label-schema.vcs-ref=$VCS_REF \ 26 | org.label-schema.vcs-url=$VCS_URL \ 27 | org.label-schema.license="Licensed Materials - Property of IBM" \ 28 | org.label-schema.schema-version="1.0" \ 29 | name="ibm-iam-operator" \ 30 | vendor="IBM" \ 31 | description="IBM IM Operator" \ 32 | summary="IBM IM Operator" \ 33 | maintainer="IBM IM Squad" \ 34 | version=$IMG_VERSION \ 35 | release=$IMG_RELEASE 36 | 37 | ENV OPERATOR=/usr/local/bin/ibm-iam-operator \ 38 | USER_UID=1001 \ 39 | USER_NAME=ibm-iam-operator 40 | 41 | WORKDIR / 42 | 43 | COPY build/_output/bin/manager ${OPERATOR} 44 | 45 | COPY licenses /licenses 46 | 47 | USER ${USER_UID} 48 | 49 | ENTRYPOINT ["/usr/local/bin/ibm-iam-operator"] 50 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | approvers: 2 | - yannizhang2019 3 | - rashmi43 4 | - Tirumalavasa 5 | - rwhundley 6 | reviewers: 7 | - yannizhang2019 8 | - rashmi43 9 | - Tirumalavasa 10 | - rwhundley 11 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | # Code generated by tool. DO NOT EDIT. 2 | # This file is used to track the info used to scaffold your project 3 | # and allow the plugins properly work. 4 | # More info: https://book.kubebuilder.io/reference/project-config.html 5 | domain: ibm.com 6 | layout: 7 | - go.kubebuilder.io/v3 8 | multigroup: true 9 | plugins: 10 | manifests.sdk.operatorframework.io/v2: {} 11 | scorecard.sdk.operatorframework.io/v2: {} 12 | projectName: ibm-iam-operator 13 | repo: github.com/IBM/ibm-iam-operator 14 | resources: 15 | - api: 16 | crdVersion: v1 17 | namespaced: true 18 | controller: true 19 | domain: ibm.com 20 | group: oidc.security 21 | kind: Client 22 | path: github.com/IBM/ibm-iam-operator/apis/oidc.security/v1 23 | version: v1 24 | - api: 25 | crdVersion: v1 26 | namespaced: true 27 | controller: true 28 | domain: ibm.com 29 | group: operator 30 | kind: Authentication 31 | path: github.com/IBM/ibm-iam-operator/apis/operator/v1alpha1 32 | version: v1alpha1 33 | version: "3" 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # ibm-iam-operator 4 | 5 | **[![Go Report Card](https://goreportcard.com/badge/github.com/IBM/ibm-iam-operator)](https://goreportcard.com/report/github.com/IBM/ibm-iam-operator)** 6 | 7 | 8 | The `ibm-iam-operator` installs the IBM Cloud Platform Common Services Identity Management (IM) service. 9 | 10 | **Important:** Do not install this operator directly. Install this operator only by using the IBM Common Service Operator. For more information about installing the IBM Common Service Operator operator, see [Installer documentation](http://ibm.biz/cpcs_opinstall) (https://www.ibm.com/support/knowledgecenter/SSHKN6/kc_welcome_cs.html). 11 | 12 | If you are using the operator as part of an IBM Cloud Pak, see the documentation for that IBM Cloud Pak to learn more about how to install and use the operator service. For more information about IBM Cloud Paks, see [IBM Cloud Paks that use Common Services](http://ibm.biz/cpcs_cloudpaks). 13 | 14 | You can use the `ibm-iam-operator` to install the authentication and authorization services for the IBM Cloud Platform Common Services. 15 | 16 | With these services, you can configure security for IBM Cloud Platform Common Services, IBM Certified Containers (IBM products), or IBM Cloud Paks that are installed. 17 | 18 | For more information about the available IBM Cloud Platform Common Services, see the [IBM Knowledge Center](http://ibm.biz/cpcsdocs). 19 | 20 | ## Supported platforms 21 | 22 | - Red Hat OpenShift Container Platform 4.2 or newer installed on one of the following platforms: 23 | 24 | - Linux x86_64 25 | - Linux on Power (ppc64le) 26 | - Linux on IBM Z and LinuxONE 27 | 28 | ## Operator versions 29 | 30 | - 4.12.0 31 | 32 | ## Prerequisites 33 | 34 | Before you install this operator, you need to first install the operator dependencies and prerequisites: 35 | 36 | - For the list of operator dependencies, see the IBM Knowledge Center [Common Services dependencies documentation](http://ibm.biz/cpcs_opdependencies). 37 | 38 | - For the list of prerequisites for installing the operator, see the IBM Knowledge Center [Preparing to install services documentation](http://ibm.biz/cpcs_opinstprereq). 39 | 40 | ## Documentation 41 | 42 | To install the operator by using the IBM Common Services Operator, follow the installation and configuration instructions that are in the IBM Knowledge Center. 43 | 44 | - If you are using the operator as part of an IBM Cloud Pak, see the documentation for that IBM Cloud Pak [IBM Cloud Paks that use Common Services](http://ibm.biz/cpcs_cloudpaks). 45 | - If you are using the operator with an IBM Containerized Software, see the IBM Cloud Platform Common Services Knowledge Center [Installer documentation](http://ibm.biz/cpcs_opinstall). 46 | 47 | ## SecurityContextConstraints Requirements 48 | 49 | The IBM IM operator service supports running with the OpenShift Container Platform default restricted Security Context Constraints (SCCs). 50 | 51 | For more information about the OpenShift Container Platform Security Context Constraints, see [Managing Security Context Constraints.](https://docs.openshift.com/container-platform/4.3/authentication/managing-security-context-constraints.html) 52 | 53 | ## PodSecurityPolicy Requirements 54 | 55 | The IM operator does not define any specific pod security requirements. 56 | 57 | ## Custom PodSecurityPolicy definition: 58 | 59 | The IM operator does not define any specific custom pod security requirements. 60 | 61 | ## Custom SecurityContextConstraints definition: 62 | 63 | The IM operator runs under a restricted security context constraint with a non root uid. 64 | 65 | ## Developer Guide 66 | 67 | For more information on how to develop and build this project, please see [DEVELOPMENT.md](DEVELOPMENT.md). 68 | -------------------------------------------------------------------------------- /apis/certmanager/v1/const.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The cert-manager Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | import "time" 20 | 21 | const ( 22 | // minimum permitted certificate duration by cert-manager 23 | MinimumCertificateDuration = time.Hour 24 | 25 | // default certificate duration if Issuer.spec.duration is not set 26 | DefaultCertificateDuration = time.Hour * 24 * 90 27 | 28 | // minimum certificate duration before certificate expiration 29 | MinimumRenewBefore = time.Minute * 5 30 | 31 | // Deprecated: the default is now 2/3 of Certificate's duration 32 | DefaultRenewBefore = time.Hour * 24 * 30 33 | ) 34 | 35 | const ( 36 | // Default index key for the Secret reference for Token authentication 37 | DefaultVaultTokenAuthSecretKey = "token" 38 | 39 | // Default mount path location for Kubernetes ServiceAccount authentication 40 | // (/v1/auth/kubernetes). The endpoint will then be called at `/login`, so 41 | // left as the default, `/v1/auth/kubernetes/login` will be called. 42 | DefaultVaultKubernetesAuthMountPath = "/v1/auth/kubernetes" 43 | 44 | // Default mount path location for client certificate authentication 45 | // (/v1/auth/cert). The endpoint will then be called at `/login`, so 46 | // left as the default, `/v1/auth/cert/login` will be called. 47 | DefaultVaultClientCertificateAuthMountPath = "/v1/auth/cert" 48 | ) 49 | -------------------------------------------------------------------------------- /apis/certmanager/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the operator v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=operator.ibm.com 20 | package v1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "cert-manager.io", Version: "v1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /apis/certmanager/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 contains API Schema definitions for the oidc.security v1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=oidc.security.ibm.com 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "certmanager.k8s.io", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /apis/oidc.security/v1/client_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // ClientSpec defines the desired state of Client 24 | type ClientSpec struct { 25 | OidcLibertyClient OidcLibertyClient `json:"oidcLibertyClient"` 26 | Secret string `json:"secret"` 27 | ClientId string `json:"clientId"` 28 | ZenAuditUrl string `json:"zenAuditUrl,omitempty"` 29 | ZenInstanceId string `json:"zenInstanceId,omitempty"` 30 | ZenProductNameUrl string `json:"zenProductNameUrl,omitempty"` 31 | Roles []string `json:"roles,omitempty"` 32 | } 33 | 34 | type OidcLibertyClient struct { 35 | RedirectUris []string `json:"redirect_uris"` 36 | TrustedUris []string `json:"trusted_uri_prefixes"` 37 | LogoutUris []string `json:"post_logout_redirect_uris"` 38 | } 39 | 40 | // ClientStatus defines the observed state of Client 41 | type ClientStatus struct { 42 | Conditions []metav1.Condition `json:"conditions,omitempty"` 43 | 44 | LastFailureTime *metav1.Time `json:"lastFailureTime,omitempty"` 45 | } 46 | 47 | const ( 48 | // ClientConditionReady indicates that a Client is ready for use. 49 | ClientConditionReady string = "Ready" 50 | ) 51 | 52 | //+kubebuilder:object:root=true 53 | //+kubebuilder:subresource:status 54 | 55 | // Client is the Schema for the clients API 56 | type Client struct { 57 | metav1.TypeMeta `json:",inline"` 58 | metav1.ObjectMeta `json:"metadata,omitempty"` 59 | 60 | Spec ClientSpec `json:"spec,omitempty"` 61 | Status ClientStatus `json:"status,omitempty"` 62 | } 63 | 64 | //+kubebuilder:object:root=true 65 | 66 | // ClientList contains a list of Client 67 | type ClientList struct { 68 | metav1.TypeMeta `json:",inline"` 69 | metav1.ListMeta `json:"metadata,omitempty"` 70 | Items []Client `json:"items"` 71 | } 72 | 73 | // ConditionStatus represents a condition's status. 74 | type ConditionStatus string 75 | 76 | // These are valid condition statuses. "ConditionTrue" means a resource is in 77 | // the condition; "ConditionFalse" means a resource is not in the condition; 78 | // "ConditionUnknown" means kubernetes can't decide if a resource is in the 79 | // condition or not. In the future, we could add other intermediate 80 | // conditions, e.g. ConditionDegraded. 81 | const ( 82 | // ConditionTrue represents the fact that a given condition is true 83 | ConditionTrue ConditionStatus = "True" 84 | 85 | // ConditionFalse represents the fact that a given condition is false 86 | ConditionFalse ConditionStatus = "False" 87 | 88 | // ConditionUnknown represents the fact that a given condition is unknown 89 | ConditionUnknown ConditionStatus = "Unknown" 90 | ) 91 | 92 | // IsCPClientCredentialsEnabled returns whether the fields required for a Client to be granted authentication tokens 93 | // with a Client ID and Secret are set. 94 | func (c *Client) IsCPClientCredentialsEnabled() bool { 95 | if len(c.Spec.ZenInstanceId) > 0 { 96 | return len(c.Spec.Roles) > 0 && len(c.Spec.ZenAuditUrl) > 0 97 | } 98 | return len(c.Spec.Roles) > 0 99 | } 100 | 101 | func init() { 102 | SchemeBuilder.Register(&Client{}, &ClientList{}) 103 | } 104 | -------------------------------------------------------------------------------- /apis/oidc.security/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 contains API Schema definitions for the oidc.security v1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=oidc.security.ibm.com 20 | package v1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "oidc.security.ibm.com", Version: "v1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /apis/oidc.security/v1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | /* 4 | Copyright 2023 IBM Corporation. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // Code generated by controller-gen. DO NOT EDIT. 20 | 21 | package v1 22 | 23 | import ( 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *Client) DeepCopyInto(out *Client) { 30 | *out = *in 31 | out.TypeMeta = in.TypeMeta 32 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 33 | in.Spec.DeepCopyInto(&out.Spec) 34 | in.Status.DeepCopyInto(&out.Status) 35 | } 36 | 37 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Client. 38 | func (in *Client) DeepCopy() *Client { 39 | if in == nil { 40 | return nil 41 | } 42 | out := new(Client) 43 | in.DeepCopyInto(out) 44 | return out 45 | } 46 | 47 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 48 | func (in *Client) DeepCopyObject() runtime.Object { 49 | if c := in.DeepCopy(); c != nil { 50 | return c 51 | } 52 | return nil 53 | } 54 | 55 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 56 | func (in *ClientList) DeepCopyInto(out *ClientList) { 57 | *out = *in 58 | out.TypeMeta = in.TypeMeta 59 | in.ListMeta.DeepCopyInto(&out.ListMeta) 60 | if in.Items != nil { 61 | in, out := &in.Items, &out.Items 62 | *out = make([]Client, len(*in)) 63 | for i := range *in { 64 | (*in)[i].DeepCopyInto(&(*out)[i]) 65 | } 66 | } 67 | } 68 | 69 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientList. 70 | func (in *ClientList) DeepCopy() *ClientList { 71 | if in == nil { 72 | return nil 73 | } 74 | out := new(ClientList) 75 | in.DeepCopyInto(out) 76 | return out 77 | } 78 | 79 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 80 | func (in *ClientList) DeepCopyObject() runtime.Object { 81 | if c := in.DeepCopy(); c != nil { 82 | return c 83 | } 84 | return nil 85 | } 86 | 87 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 88 | func (in *ClientSpec) DeepCopyInto(out *ClientSpec) { 89 | *out = *in 90 | in.OidcLibertyClient.DeepCopyInto(&out.OidcLibertyClient) 91 | if in.Roles != nil { 92 | in, out := &in.Roles, &out.Roles 93 | *out = make([]string, len(*in)) 94 | copy(*out, *in) 95 | } 96 | } 97 | 98 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientSpec. 99 | func (in *ClientSpec) DeepCopy() *ClientSpec { 100 | if in == nil { 101 | return nil 102 | } 103 | out := new(ClientSpec) 104 | in.DeepCopyInto(out) 105 | return out 106 | } 107 | 108 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 109 | func (in *ClientStatus) DeepCopyInto(out *ClientStatus) { 110 | *out = *in 111 | if in.Conditions != nil { 112 | in, out := &in.Conditions, &out.Conditions 113 | *out = make([]metav1.Condition, len(*in)) 114 | for i := range *in { 115 | (*in)[i].DeepCopyInto(&(*out)[i]) 116 | } 117 | } 118 | if in.LastFailureTime != nil { 119 | in, out := &in.LastFailureTime, &out.LastFailureTime 120 | *out = (*in).DeepCopy() 121 | } 122 | } 123 | 124 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientStatus. 125 | func (in *ClientStatus) DeepCopy() *ClientStatus { 126 | if in == nil { 127 | return nil 128 | } 129 | out := new(ClientStatus) 130 | in.DeepCopyInto(out) 131 | return out 132 | } 133 | 134 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 135 | func (in *OidcLibertyClient) DeepCopyInto(out *OidcLibertyClient) { 136 | *out = *in 137 | if in.RedirectUris != nil { 138 | in, out := &in.RedirectUris, &out.RedirectUris 139 | *out = make([]string, len(*in)) 140 | copy(*out, *in) 141 | } 142 | if in.TrustedUris != nil { 143 | in, out := &in.TrustedUris, &out.TrustedUris 144 | *out = make([]string, len(*in)) 145 | copy(*out, *in) 146 | } 147 | if in.LogoutUris != nil { 148 | in, out := &in.LogoutUris, &out.LogoutUris 149 | *out = make([]string, len(*in)) 150 | copy(*out, *in) 151 | } 152 | } 153 | 154 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OidcLibertyClient. 155 | func (in *OidcLibertyClient) DeepCopy() *OidcLibertyClient { 156 | if in == nil { 157 | return nil 158 | } 159 | out := new(OidcLibertyClient) 160 | in.DeepCopyInto(out) 161 | return out 162 | } 163 | -------------------------------------------------------------------------------- /apis/operator/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the operator v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=operator.ibm.com 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "operator.ibm.com", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | 37 | // The following is a dedicated scheme for when ODLM is available on the cluster; it shares the same 38 | // GroupVersion as Authentication 39 | ODLMEnabledSchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 40 | AddODLMEnabledToScheme = ODLMEnabledSchemeBuilder.AddToScheme 41 | ) 42 | -------------------------------------------------------------------------------- /apis/zen.cpd.ibm.com/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package zenv1 contains API Schema definitions for the zen.cpd.ibm.com v1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=zen.cpd.ibm.com 20 | package zenv1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "zen.cpd.ibm.com", Version: "v1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /apis/zen.cpd.ibm.com/v1/zenextension_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 IBM Corporation. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package zenv1 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/api/meta" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | //+kubebuilder:object:root=true 25 | //+kubebuilder:subresource:status 26 | //+kubebuilder:resource:path=zenextensions,scope=Namespaced 27 | 28 | // ZenExtension is the Schema for the zen extension API. The spec is omitted 29 | // from this struct because the ZenExtension API does not have any guaranteed 30 | // structure, which is incompatible with controller-gen code generation. 31 | type ZenExtension struct { 32 | metav1.TypeMeta `json:",inline"` 33 | metav1.ObjectMeta `json:"metadata,omitempty"` 34 | 35 | // +kubebuilder:pruning:PreserveUnknownFields 36 | Status ZenExtensionStatus `json:"status,omitempty"` 37 | } 38 | 39 | func (z *ZenExtension) Ready() bool { 40 | return z.Status.AllExtensionsProcessed() 41 | } 42 | 43 | func (z *ZenExtension) NotReady() bool { 44 | return !z.Ready() 45 | } 46 | 47 | type ZenExtensionStatus struct { 48 | Conditions []metav1.Condition `json:"conditions,omitempty"` 49 | Message string `json:"message,omitempty"` 50 | Status string `json:"zenExtensionStatus,omitempty"` 51 | } 52 | 53 | type ConditionType string 54 | 55 | const ConditionTypeFailure string = "Failure" 56 | const ConditionTypeSuccessful string = "Successful" 57 | const ConditionTypeRunning string = "Running" 58 | const ZenExtensionStatusCompleted string = "Completed" 59 | 60 | func (z ZenExtensionStatus) AllExtensionsProcessed() bool { 61 | return meta.IsStatusConditionTrue(z.Conditions, ConditionTypeSuccessful) && 62 | meta.IsStatusConditionFalse(z.Conditions, ConditionTypeFailure) && 63 | meta.IsStatusConditionTrue(z.Conditions, ConditionTypeRunning) && 64 | z.Status == ZenExtensionStatusCompleted 65 | } 66 | 67 | //+kubebuilder:object:root=true 68 | 69 | // ZenExtensionList contains a list of ZenExtension 70 | type ZenExtensionList struct { 71 | metav1.TypeMeta `json:",inline"` 72 | metav1.ListMeta `json:"metadata,omitempty"` 73 | Items []ZenExtension `json:"items"` 74 | } 75 | 76 | func init() { 77 | SchemeBuilder.Register(&ZenExtension{}, &ZenExtensionList{}) 78 | } 79 | -------------------------------------------------------------------------------- /apis/zen.cpd.ibm.com/v1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | /* 4 | Copyright 2023 IBM Corporation. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // Code generated by controller-gen. DO NOT EDIT. 20 | 21 | package zenv1 22 | 23 | import ( 24 | "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *ZenExtension) DeepCopyInto(out *ZenExtension) { 30 | *out = *in 31 | out.TypeMeta = in.TypeMeta 32 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 33 | in.Status.DeepCopyInto(&out.Status) 34 | } 35 | 36 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ZenExtension. 37 | func (in *ZenExtension) DeepCopy() *ZenExtension { 38 | if in == nil { 39 | return nil 40 | } 41 | out := new(ZenExtension) 42 | in.DeepCopyInto(out) 43 | return out 44 | } 45 | 46 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 47 | func (in *ZenExtension) DeepCopyObject() runtime.Object { 48 | if c := in.DeepCopy(); c != nil { 49 | return c 50 | } 51 | return nil 52 | } 53 | 54 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 55 | func (in *ZenExtensionList) DeepCopyInto(out *ZenExtensionList) { 56 | *out = *in 57 | out.TypeMeta = in.TypeMeta 58 | in.ListMeta.DeepCopyInto(&out.ListMeta) 59 | if in.Items != nil { 60 | in, out := &in.Items, &out.Items 61 | *out = make([]ZenExtension, len(*in)) 62 | for i := range *in { 63 | (*in)[i].DeepCopyInto(&(*out)[i]) 64 | } 65 | } 66 | } 67 | 68 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ZenExtensionList. 69 | func (in *ZenExtensionList) DeepCopy() *ZenExtensionList { 70 | if in == nil { 71 | return nil 72 | } 73 | out := new(ZenExtensionList) 74 | in.DeepCopyInto(out) 75 | return out 76 | } 77 | 78 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 79 | func (in *ZenExtensionList) DeepCopyObject() runtime.Object { 80 | if c := in.DeepCopy(); c != nil { 81 | return c 82 | } 83 | return nil 84 | } 85 | 86 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 87 | func (in *ZenExtensionStatus) DeepCopyInto(out *ZenExtensionStatus) { 88 | *out = *in 89 | if in.Conditions != nil { 90 | in, out := &in.Conditions, &out.Conditions 91 | *out = make([]v1.Condition, len(*in)) 92 | for i := range *in { 93 | (*in)[i].DeepCopyInto(&(*out)[i]) 94 | } 95 | } 96 | } 97 | 98 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ZenExtensionStatus. 99 | func (in *ZenExtensionStatus) DeepCopy() *ZenExtensionStatus { 100 | if in == nil { 101 | return nil 102 | } 103 | out := new(ZenExtensionStatus) 104 | in.DeepCopyInto(out) 105 | return out 106 | } 107 | -------------------------------------------------------------------------------- /base_images.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "imageType": "external", 4 | "sourceRepo": "registry.access.redhat.com", 5 | "sourceNamespace": "ubi9", 6 | "sourceImage": "ubi-minimal", 7 | "destStage": "edge", 8 | "destNamespace": "build-images", 9 | "destImage": "ubi9-minimal", 10 | "tag": "9.6-1747218906", 11 | "updatePackages": [] 12 | }, 13 | { 14 | "imageType": "external", 15 | "sourceRepo": "registry.access.redhat.com", 16 | "sourceNamespace": "ubi9", 17 | "sourceImage": "ubi", 18 | "destStage": "edge", 19 | "destNamespace": "build-images", 20 | "destImage": "ubi9", 21 | "tag": "9.6-1747219013", 22 | "updatePackages": [] 23 | }, 24 | { 25 | "imageType": "external", 26 | "sourceRepo": "registry.access.redhat.com", 27 | "sourceNamespace": "ubi9", 28 | "sourceImage": "ubi-micro", 29 | "destStage": "edge", 30 | "destNamespace": "build-images", 31 | "destImage": "ubi9-micro", 32 | "tag": "9.6-1745521186", 33 | "updatePackages": [] 34 | }, 35 | { 36 | "imageType": "node", 37 | "sourceImage": "ubi9-minimal", 38 | "sourceTag": "9.6-1747218906", 39 | "destImage": "node-v20-ubi9-minimal", 40 | "nodeVersion": "20.19.2" 41 | }, 42 | { 43 | "imageType": "node", 44 | "sourceImage": "ubi9-minimal", 45 | "sourceTag": "9.6-1747218906", 46 | "destImage": "node-v22-ubi9-minimal", 47 | "nodeVersion": "22.15.1" 48 | } 49 | ] 50 | -------------------------------------------------------------------------------- /bundle.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | # Core bundle labels. 4 | LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 5 | LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ 6 | LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ 7 | LABEL operators.operatorframework.io.bundle.package.v1=ibm-iam-operator 8 | LABEL operators.operatorframework.io.bundle.channels.v1=v4.12 9 | LABEL operators.operatorframework.io.bundle.channel.default.v1=v4.12 10 | LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.37.0 11 | LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 12 | LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v3 13 | 14 | # Labels for testing. 15 | LABEL operators.operatorframework.io.test.mediatype.v1=scorecard+v1 16 | LABEL operators.operatorframework.io.test.config.v1=tests/scorecard/ 17 | 18 | # Copy files to locations specified by labels. 19 | COPY bundle/manifests /manifests/ 20 | COPY bundle/metadata /metadata/ 21 | COPY bundle/tests/scorecard /tests/scorecard/ 22 | -------------------------------------------------------------------------------- /bundle/manifests/oidc.security.ibm.com_clients.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app.kubernetes.io/instance: ibm-iam-operator 7 | app.kubernetes.io/managed-by: ibm-iam-operator 8 | app.kubernetes.io/name: ibm-iam-operator 9 | name: clients.oidc.security.ibm.com 10 | spec: 11 | conversion: 12 | strategy: None 13 | group: oidc.security.ibm.com 14 | names: 15 | kind: Client 16 | listKind: ClientList 17 | plural: clients 18 | singular: client 19 | scope: Namespaced 20 | versions: 21 | - name: v1 22 | schema: 23 | openAPIV3Schema: 24 | description: The Client custom resource is used for registering clients with 25 | Identity Management service 26 | properties: 27 | apiVersion: 28 | description: 'APIVersion defines the versioned schema of this representation 29 | of an object. Servers should convert recognized schemas to the latest 30 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' 31 | type: string 32 | kind: 33 | description: 'Kind is a string value representing the REST resource this 34 | object represents. Servers may infer this from the endpoint the client 35 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | type: object 41 | x-kubernetes-preserve-unknown-fields: true 42 | status: 43 | type: object 44 | x-kubernetes-preserve-unknown-fields: true 45 | type: object 46 | served: true 47 | storage: true 48 | subresources: 49 | status: {} 50 | status: 51 | acceptedNames: 52 | kind: Client 53 | listKind: ClientList 54 | plural: clients 55 | singular: client 56 | conditions: [] 57 | storedVersions: 58 | - v1 59 | -------------------------------------------------------------------------------- /bundle/metadata/annotations.yaml: -------------------------------------------------------------------------------- 1 | annotations: 2 | # Core bundle annotations. 3 | operators.operatorframework.io.bundle.mediatype.v1: registry+v1 4 | operators.operatorframework.io.bundle.manifests.v1: manifests/ 5 | operators.operatorframework.io.bundle.metadata.v1: metadata/ 6 | operators.operatorframework.io.bundle.package.v1: ibm-iam-operator 7 | operators.operatorframework.io.bundle.channels.v1: v4.12 8 | operators.operatorframework.io.bundle.channel.default.v1: v4.12 9 | operators.operatorframework.io.metrics.builder: operator-sdk-v1.37.0 10 | operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 11 | operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v3 12 | # Annotations for testing. 13 | operators.operatorframework.io.test.mediatype.v1: scorecard+v1 14 | operators.operatorframework.io.test.config.v1: tests/scorecard/ 15 | com.redhat.openshift.versions: v4.12-v4.18 16 | -------------------------------------------------------------------------------- /bundle/tests/scorecard/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: 8 | - entrypoint: 9 | - scorecard-test 10 | - basic-check-spec 11 | image: quay.io/operator-framework/scorecard-test:v1.29.0 12 | labels: 13 | suite: basic 14 | test: basic-check-spec-test 15 | storage: 16 | spec: 17 | mountPath: {} 18 | - entrypoint: 19 | - scorecard-test 20 | - olm-bundle-validation 21 | image: quay.io/operator-framework/scorecard-test:v1.29.0 22 | labels: 23 | suite: olm 24 | test: olm-bundle-validation-test 25 | storage: 26 | spec: 27 | mountPath: {} 28 | - entrypoint: 29 | - scorecard-test 30 | - olm-crds-have-validation 31 | image: quay.io/operator-framework/scorecard-test:v1.29.0 32 | labels: 33 | suite: olm 34 | test: olm-crds-have-validation-test 35 | storage: 36 | spec: 37 | mountPath: {} 38 | - entrypoint: 39 | - scorecard-test 40 | - olm-crds-have-resources 41 | image: quay.io/operator-framework/scorecard-test:v1.29.0 42 | labels: 43 | suite: olm 44 | test: olm-crds-have-resources-test 45 | storage: 46 | spec: 47 | mountPath: {} 48 | - entrypoint: 49 | - scorecard-test 50 | - olm-spec-descriptors 51 | image: quay.io/operator-framework/scorecard-test:v1.29.0 52 | labels: 53 | suite: olm 54 | test: olm-spec-descriptors-test 55 | storage: 56 | spec: 57 | mountPath: {} 58 | - entrypoint: 59 | - scorecard-test 60 | - olm-status-descriptors 61 | image: quay.io/operator-framework/scorecard-test:v1.29.0 62 | labels: 63 | suite: olm 64 | test: olm-status-descriptors-test 65 | storage: 66 | spec: 67 | mountPath: {} 68 | storage: 69 | spec: 70 | mountPath: {} 71 | -------------------------------------------------------------------------------- /common/Makefile.common.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ############################################################ 16 | # install git hooks 17 | ############################################################ 18 | INSTALL_HOOKS := $(shell find .git/hooks -type l -exec rm {} \; && \ 19 | find common/scripts/.githooks -type f -exec ln -sf ../../{} .git/hooks/ \; ) 20 | 21 | ############################################################ 22 | # GKE section 23 | ############################################################ 24 | PROJECT ?= oceanic-guard-191815 25 | ZONE ?= us-east5-c 26 | CLUSTER ?= bedrock-prow 27 | 28 | activate-serviceaccount: 29 | ifdef GOOGLE_APPLICATION_CREDENTIALS 30 | @gcloud auth activate-service-account --key-file="$(GOOGLE_APPLICATION_CREDENTIALS)" 31 | @gcloud components install gke-gcloud-auth-plugin --quiet 32 | endif 33 | 34 | get-cluster-credentials: activate-serviceaccount 35 | @gcloud container clusters get-credentials "$(CLUSTER)" --project="$(PROJECT)" --zone="$(ZONE)" 36 | 37 | config-docker: get-cluster-credentials 38 | @common/scripts/config_docker.sh 39 | 40 | ############################################################ 41 | # lint section 42 | ############################################################ 43 | 44 | FINDFILES=find . \( -path ./.git -o -path ./.github \) -prune -o -type f 45 | XARGS = xargs -0 ${XARGS_FLAGS} 46 | CLEANXARGS = xargs ${XARGS_FLAGS} 47 | 48 | lint-dockerfiles: 49 | @${FINDFILES} -name 'Dockerfile*' -print0 | ${XARGS} hadolint -c ./common/config/.hadolint.yml 50 | 51 | lint-scripts: 52 | @${FINDFILES} -name '*.sh' -print0 | ${XARGS} shellcheck --exclude=SC2086,SC2027,SC1078,SC2219,SC1079,SC1011,SC2046,SC2162,SC2026 53 | 54 | lint-yaml: 55 | @${FINDFILES} \( -name '*.yml' -o -name '*.yaml' \) -print0 | ${XARGS} grep -L -e "{{" | ${CLEANXARGS} yamllint -c ./common/config/.yamllint.yml 56 | 57 | lint-helm: 58 | @${FINDFILES} -name 'Chart.yaml' -print0 | ${XARGS} -L 1 dirname | ${CLEANXARGS} helm lint 59 | 60 | lint-copyright-banner: 61 | @${FINDFILES} \( -name '*.go' -o -name '*.cc' -o -name '*.h' -o -name '*.proto' -o -name '*.py' -o -name '*.sh' \) \( ! \( -name '*.gen.go' -o -name '*.pb.go' -o -name '*_pb2.py' \) \) -print0 |\ 62 | ${XARGS} common/scripts/lint_copyright_banner.sh 63 | 64 | lint-go: 65 | @${FINDFILES} -name '*.go' \( ! \( -name '*.gen.go' -o -name '*.pb.go' \) \) -print0 | ${XARGS} common/scripts/lint_go.sh 66 | 67 | lint-python: 68 | @${FINDFILES} -name '*.py' \( ! \( -name '*_pb2.py' \) \) -print0 | ${XARGS} autopep8 --max-line-length 160 --exit-code -d 69 | 70 | lint-markdown: 71 | @${FINDFILES} -name '*.md' -print0 | ${XARGS} mdl --ignore-front-matter --style common/config/mdl.rb 72 | ifdef MARKDOWN_LINT_WHITELIST 73 | @${FINDFILES} -name '*.md' -print0 | ${XARGS} awesome_bot --skip-save-results --allow_ssl --allow-timeout --allow-dupe --allow-redirect --white-list ${MARKDOWN_LINT_WHITELIST} 74 | else 75 | @${FINDFILES} -name '*.md' -print0 | ${XARGS} awesome_bot --skip-save-results --allow_ssl --allow-timeout --allow-dupe --allow-redirect 76 | endif 77 | 78 | lint-sass: 79 | @${FINDFILES} -name '*.scss' -print0 | ${XARGS} sass-lint -c common/config/sass-lint.yml --verbose 80 | 81 | lint-typescript: 82 | @${FINDFILES} -name '*.ts' -print0 | ${XARGS} tslint -c common/config/tslint.json 83 | 84 | lint-protos: 85 | @$(FINDFILES) -name '*.proto' -print0 | $(XARGS) -L 1 prototool lint --protoc-bin-path=/usr/bin/protoc 86 | 87 | #lint-all: lint-dockerfiles lint-scripts lint-yaml lint-helm lint-copyright-banner lint-go int-python lint-markdown lint-sass lint-typescript lint-protos 88 | 89 | lint-all: lint-dockerfiles lint-scripts lint-yaml lint-helm lint-copyright-banner 90 | 91 | format-go: 92 | @${FINDFILES} -name '*.go' \( ! \( -name '*.gen.go' -o -name '*.pb.go' \) \) -print0 | ${XARGS} goimports -w -local "github.com/IBM" 93 | 94 | format-python: 95 | @${FINDFILES} -name '*.py' -print0 | ${XARGS} autopep8 --max-line-length 160 --aggressive --aggressive -i 96 | 97 | format-protos: 98 | @$(FINDFILES) -name '*.proto' -print0 | $(XARGS) -L 1 prototool format -w 99 | 100 | 101 | .PHONY: lint-dockerfiles lint-scripts lint-yaml lint-copyright-banner lint-go lint-python lint-helm lint-markdown lint-sass lint-typescript lint-protos lint-all format-go format-python format-protos csv-gen bundle install-operator-courier verify-bundle redhat-certify-ready config-docker 102 | -------------------------------------------------------------------------------- /common/config/.hadolint.yml: -------------------------------------------------------------------------------- 1 | ignored: 2 | - FAKE_DL3003 3 | - DL3006 4 | - DL3007 5 | - DL3047 6 | 7 | trustedRegistries: 8 | - gcr.io 9 | - docker.io 10 | - quay.io 11 | - icr.io 12 | - registry.access.redhat.com 13 | - docker-na-public.artifactory.swg-devops.com 14 | -------------------------------------------------------------------------------- /common/config/.yamllint.yml: -------------------------------------------------------------------------------- 1 | rules: 2 | braces: disable 3 | brackets: disable 4 | colons: enable 5 | commas: disable 6 | comments: disable 7 | comments-indentation: disable 8 | document-end: disable 9 | document-start: disable 10 | empty-lines: disable 11 | empty-values: enable 12 | hyphens: enable 13 | indentation: disable 14 | key-duplicates: enable 15 | key-ordering: disable 16 | line-length: disable 17 | new-line-at-end-of-file: disable 18 | new-lines: enable 19 | octal-values: enable 20 | quoted-strings: disable 21 | trailing-spaces: disable 22 | truthy: disable -------------------------------------------------------------------------------- /common/config/mdl.rb: -------------------------------------------------------------------------------- 1 | all 2 | rule 'MD002', :level => 1 3 | rule 'MD007', :indent => 4 4 | rule 'MD013', :line_length => 160, :code_blocks => false, :tables => false 5 | rule 'MD026', :punctuation => ".,;:!" 6 | exclude_rule 'MD013' 7 | exclude_rule 'MD014' 8 | exclude_rule 'MD030' 9 | exclude_rule 'MD032' 10 | exclude_rule 'MD033' 11 | exclude_rule 'MD041' 12 | exclude_rule 'MD046' -------------------------------------------------------------------------------- /common/config/sass-lint.yml: -------------------------------------------------------------------------------- 1 | ######################### 2 | ## Config for sass-lint 3 | ######################### 4 | # Linter Options 5 | options: 6 | # Don't merge default rules 7 | merge-default-rules: false 8 | # Raise an error if more than 50 warnings are generated 9 | max-warnings: 500 10 | # Rule Configuration 11 | rules: 12 | attribute-quotes: 13 | - 2 14 | - 15 | include: false 16 | bem-depth: 2 17 | border-zero: 2 18 | brace-style: 2 19 | class-name-format: 2 20 | clean-import-paths: 2 21 | declarations-before-nesting: 2 22 | empty-args: 2 23 | empty-line-between-blocks: 2 24 | extends-before-declarations: 2 25 | extends-before-mixins: 2 26 | final-newline: 2 27 | force-attribute-nesting: 0 28 | force-element-nesting: 0 29 | force-pseudo-nesting: 0 30 | function-name-format: 2 31 | hex-length: 0 32 | hex-notation: 2 33 | id-name-format: 2 34 | indentation: 35 | - 2 36 | - 37 | size: 4 38 | leading-zero: 39 | - 2 40 | - 41 | include: false 42 | max-file-line-count: 0 43 | max-file-length: 0 44 | mixins-before-declarations: 2 45 | no-attribute-selectors: 0 46 | no-color-hex: 0 47 | no-color-keywords: 0 48 | no-color-literals: 0 49 | no-combinators: 0 50 | no-css-comments: 2 51 | no-debug: 2 52 | no-disallowed-properties: 2 53 | no-duplicate-properties: 2 54 | no-empty-rulesets: 2 55 | no-extends: 2 56 | no-ids: 0 57 | no-invalid-hex: 2 58 | no-important: 0 59 | no-mergeable-selectors: 2 60 | no-misspelled-properties: 2 61 | no-qualifying-elements: 0 62 | no-trailing-whitespace: 2 63 | no-trailing-zero: 2 64 | no-transition-all: 0 65 | no-url-domains: 2 66 | no-url-protocols: 2 67 | no-warn: 2 68 | one-declaration-per-line: 2 69 | placeholder-in-extend: 2 70 | placeholder-name-format: 2 71 | property-sort-order: 0 72 | property-units: 2 73 | pseudo-element: 2 74 | quotes: 75 | - 2 76 | - 77 | style: double 78 | shorthand-values: 2 79 | single-line-per-selector: 0 80 | space-after-bang: 2 81 | space-after-colon: 2 82 | space-after-comma: 2 83 | space-around-operator: 2 84 | space-before-bang: 2 85 | space-before-brace: 2 86 | space-before-colon: 2 87 | space-between-parens: 2 88 | trailing-semicolon: 2 89 | url-quotes: 2 90 | variable-for-property: 91 | - 0 92 | - 93 | properties: 94 | - color 95 | - background-color 96 | - fill 97 | variable-name-format: 0 98 | zero-unit: 2 -------------------------------------------------------------------------------- /common/config/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "rules": { 7 | "max-line-length": { 8 | "options": [160] 9 | }, 10 | "arrow-parens": false, 11 | "new-parens": true, 12 | "no-arg": true, 13 | "no-bitwise": true, 14 | "no-conditional-assignment": true, 15 | "no-consecutive-blank-lines": true, 16 | "no-console": { 17 | "severity": "warning", 18 | "options": ["debug", "info", "log", "time", "timeEnd", "trace"] 19 | }, 20 | "no-shadowed-variable": false, 21 | "eofline": false 22 | }, 23 | "jsRules": {}, 24 | "rulesDirectory": [] 25 | } -------------------------------------------------------------------------------- /common/manifest.yaml: -------------------------------------------------------------------------------- 1 | image: __IMAGE_REPO__/__IMAGE_NAME__:__RELEASE_TAG__ 2 | manifests: 3 | - image: __IMAGE_REPO__/__IMAGE_NAME__-amd64:__RELEASE_TAG__ 4 | platform: 5 | architecture: amd64 6 | os: linux 7 | - image: __IMAGE_REPO__/__IMAGE_NAME__-ppc64le:__RELEASE_TAG__ 8 | platform: 9 | architecture: ppc64le 10 | os: linux 11 | - image: __IMAGE_REPO__/__IMAGE_NAME__-s390x:__RELEASE_TAG__ 12 | platform: 13 | architecture: s390x 14 | os: linux 15 | -------------------------------------------------------------------------------- /common/scripts/.githooks/make_lint-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Launches fmt and lint checks 18 | make lint-all 19 | #make lint-scripts 20 | -------------------------------------------------------------------------------- /common/scripts/.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This hook is called with the following parameters: 18 | # 19 | # $1 -- Name of the remote to which the push is being done 20 | # $2 -- URL to which the push is being done 21 | # 22 | # If pushing without using a named remote those arguments will be equal. 23 | # 24 | # Information about the commits which are being pushed is supplied as lines to 25 | # the standard input in the form: 26 | # 27 | # 28 | # 29 | 30 | remote="$1" 31 | url="$2" 32 | 33 | .git/hooks/make_lint-all.sh 34 | gofmt -w -s -l . 35 | 36 | exit $? 37 | -------------------------------------------------------------------------------- /common/scripts/config_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2020 IBM Corporation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | KUBECTL=$(command -v kubectl) 19 | DOCKER_REGISTRY="docker-na-public.artifactory.swg-devops.com" 20 | DOCKER_REGISTRIES=( 21 | "${DOCKER_REGISTRY}/hyc-cloud-private-integration-docker-local" 22 | "${DOCKER_REGISTRY}/hyc-cloud-private-edge-docker-local" 23 | ) 24 | 25 | DOCKER_USERNAME=$(${KUBECTL} -n default get secret artifactory-cred -o jsonpath='{.data.username}' | base64 --decode) 26 | DOCKER_PASSWORD=$(${KUBECTL} -n default get secret artifactory-cred -o jsonpath='{.data.password}' | base64 --decode) 27 | 28 | # support other container tools, e.g. podman 29 | CONTAINER_CLI=${CONTAINER_CLI:-docker} 30 | 31 | # login the docker registry 32 | 33 | for registry in "${DOCKER_REGISTRIES[@]}" 34 | do 35 | ${CONTAINER_CLI} login "${registry}" -u "${DOCKER_USERNAME}" -p "${DOCKER_PASSWORD}" 36 | done 37 | -------------------------------------------------------------------------------- /common/scripts/gobuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2020 IBM Corporation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # This script builds and version stamps the output 19 | 20 | VERBOSE=${VERBOSE:-"0"} 21 | V="" 22 | if [[ "${VERBOSE}" == "1" ]];then 23 | V="-x" 24 | set -x 25 | fi 26 | 27 | OUT=${1:?"output path"} 28 | shift 29 | 30 | set -e 31 | 32 | BUILD_GOOS=${GOOS:-linux} 33 | BUILD_GOARCH=${GOARCH:-amd64} 34 | GOBINARY=${GOBINARY:-go} 35 | BUILDINFO=${BUILDINFO:-""} 36 | STATIC=${STATIC:-1} 37 | GOBUILDFLAGS=${GOBUILDFLAGS:-} 38 | GCFLAGS=${GCFLAGS:-} 39 | LDFLAGS=${LDFLAGS:-"-extldflags -static"} 40 | # Split GOBUILDFLAGS by spaces into an array called GOBUILDFLAGS_ARRAY. 41 | IFS=' ' read -r -a GOBUILDFLAGS_ARRAY <<< "$GOBUILDFLAGS" 42 | 43 | export CGO_ENABLED=0 44 | 45 | if [[ "${STATIC}" != "1" ]];then 46 | LDFLAGS="" 47 | fi 48 | 49 | time GOOS=${BUILD_GOOS} GOARCH=${BUILD_GOARCH} ${GOBINARY} build \ 50 | ${V} "${GOBUILDFLAGS_ARRAY[@]}" ${GCFLAGS:+-gcflags "${GCFLAGS}"} \ 51 | -o "${OUT}" \ 52 | -ldflags "${LDFLAGS}" "${@}" 53 | -------------------------------------------------------------------------------- /common/scripts/install-operator-sdk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2020 IBM Corporation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | echo ">>> Installing Operator SDK" 19 | 20 | # Use version 0.10.0 21 | RELEASE_VERSION=v0.12.0 22 | # Download binary 23 | curl -LO https://github.com/operator-framework/operator-sdk/releases/download/${RELEASE_VERSION}/operator-sdk-${RELEASE_VERSION}-x86_64-linux-gnu 24 | # Install binary 25 | chmod +x operator-sdk-${RELEASE_VERSION}-x86_64-linux-gnu && mkdir -p /usr/local/bin/ && cp operator-sdk-${RELEASE_VERSION}-x86_64-linux-gnu /usr/local/bin/operator-sdk && rm operator-sdk-${RELEASE_VERSION}-x86_64-linux-gnu 26 | -------------------------------------------------------------------------------- /common/scripts/lint-csv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2020 IBM Corporation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | STATUS=0 19 | ARCH=$(uname -m) 20 | OS=$(uname -s) 21 | VERSION="${VERSION:-}" 22 | if [[ -z "${VERSION}" ]]; then 23 | >&2 echo "VERSION environtment variable must be set to a non-empty value" 24 | exit 1 25 | fi 26 | 27 | if [[ $ARCH == "x86_64" && $OS == "Linux" ]]; then 28 | curl -L -o /tmp/jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 29 | curl -L -o /tmp/yq https://github.com/mikefarah/yq/releases/download/3.3.0/yq_linux_amd64 30 | chmod +x /tmp/jq /tmp/yq 31 | elif [[ $ARCH == "x86_64" && $OS == "Darwin" ]]; then 32 | curl -L -o /tmp/jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-osx-amd64 33 | curl -L -o /tmp/yq https://github.com/mikefarah/yq/releases/download/3.3.0/yq_darwin_amd64 34 | chmod +x /tmp/jq /tmp/yq 35 | else 36 | exit 0 37 | fi 38 | 39 | CSV_PATH=deploy/olm-catalog/ibm-iam-operator/${VERSION}/ibm-iam-operator.v${VERSION}.clusterserviceversion.yaml 40 | 41 | # Lint alm-examples 42 | echo "Lint alm-examples" 43 | /tmp/yq r "$CSV_PATH" metadata.annotations.alm-examples | /tmp/jq . >/dev/null || STATUS=1 44 | 45 | # Lint yamls, only CS Operator needs this part 46 | for section in csNamespace csOperandConfig csOperandRegistry odlmSubscription; do 47 | echo "Lint $section" 48 | /tmp/yq r "$CSV_PATH" metadata.annotations."$section" | /tmp/yq r - >/dev/null || STATUS=1 49 | done 50 | 51 | sections=$(/tmp/yq r "$CSV_PATH" metadata.annotations.extraResources) 52 | for section in ${sections//,/ }; do 53 | echo "Lint $section" 54 | /tmp/yq r "$CSV_PATH" metadata.annotations."$section" | /tmp/yq r - >/dev/null || STATUS=1 55 | done 56 | 57 | exit $STATUS 58 | -------------------------------------------------------------------------------- /common/scripts/lint_copyright_banner.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2020 IBM Corporation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | set -e 19 | 20 | ec=0 21 | for fn in "$@"; do 22 | if ! (grep -q "Apache License, Version 2" "${fn}"); then 23 | echo "Missing license: ${fn}" 24 | ec=1 25 | fi 26 | 27 | if ! (grep -q "Copyright" "${fn}"); then 28 | echo "Missing copyright: ${fn}" 29 | ec=1 30 | fi 31 | done 32 | 33 | exit $ec 34 | -------------------------------------------------------------------------------- /common/scripts/lint_go.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2020 IBM Corporation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | GOGC=25 golangci-lint run -c ./common/config/.golangci.yml 19 | -------------------------------------------------------------------------------- /common/scripts/multiarch_image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2024 IBM Corporation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # This script build and push multiarch(amd64, ppc64le and s390x) image for the one specified by 19 | # IMAGE_REPO, IMAGE_NAME and VERSION. 20 | # It assumes the specified image for each platform is already pushed into corresponding docker registry. 21 | 22 | ALL_PLATFORMS="amd64 ppc64le s390x" 23 | 24 | IMAGE_REPO=${1} 25 | IMAGE_NAME=${2} 26 | VERSION=${3-"$(git describe --exact-match 2> /dev/null || git describe --match=$(git rev-parse --short=8 HEAD) --always --dirty --abbrev=8)"} 27 | RELEASE_VERSION=${4:-4.12.0} 28 | MAX_PULLING_RETRY=${MAX_PULLING_RETRY-10} 29 | RETRY_INTERVAL=${RETRY_INTERVAL-10} 30 | # support other container tools, e.g. podman 31 | CONTAINER_CLI=${CONTAINER_CLI:-docker} 32 | 33 | # Loop until the image for each single platform is ready in the docker registry. 34 | # TODO: remove this if prow job support dependency. 35 | for arch in ${ALL_PLATFORMS} 36 | do 37 | for i in $(seq 1 "${MAX_PULLING_RETRY}") 38 | do 39 | echo "Checking image '${IMAGE_REPO}'/'${IMAGE_NAME}'-'${arch}':'${VERSION}'..." 40 | ${CONTAINER_CLI} inspect "${IMAGE_REPO}"/"${IMAGE_NAME}"-"${arch}":"${VERSION}" && break 41 | sleep "${RETRY_INTERVAL}" 42 | if [ "${i}" -eq "${MAX_PULLING_RETRY}" ]; then 43 | echo "Failed to find image '${IMAGE_REPO}'/'${IMAGE_NAME}'-'${arch}':'${VERSION}'!!!" 44 | exit 1 45 | fi 46 | done 47 | done 48 | 49 | if [[ -n "$($CONTAINER_CLI images -f reference="${IMAGE_REPO}/${IMAGE_NAME}:${RELEASE_VERSION}" --format='{{.ID}}')" ]] 50 | then 51 | echo "Remove local image with conflicting reference ${IMAGE_REPO}/${IMAGE_NAME}:${RELEASE_VERSION}" 52 | ${CONTAINER_CLI} rmi "${IMAGE_REPO}/${IMAGE_NAME}:${RELEASE_VERSION}" 53 | fi 54 | if [[ -n "$($CONTAINER_CLI images -f reference="${IMAGE_REPO}/${IMAGE_NAME}:latest" --format='{{.ID}}')" ]] 55 | then 56 | echo "Remove local image with conflicting reference ${IMAGE_REPO}/${IMAGE_NAME}:latest" 57 | ${CONTAINER_CLI} rmi "${IMAGE_REPO}/${IMAGE_NAME}:latest" 58 | fi 59 | 60 | # create multi-arch manifest 61 | echo "Creating the multi-arch image manifest for ${IMAGE_REPO}/${IMAGE_NAME}:${RELEASE_VERSION}..." 62 | ${CONTAINER_CLI} manifest create "${IMAGE_REPO}"/"${IMAGE_NAME}":"${RELEASE_VERSION}" \ 63 | "${IMAGE_REPO}"/"${IMAGE_NAME}"-amd64:"${VERSION}" \ 64 | "${IMAGE_REPO}"/"${IMAGE_NAME}"-ppc64le:"${VERSION}" \ 65 | "${IMAGE_REPO}"/"${IMAGE_NAME}"-s390x:"${VERSION}" 66 | echo "Creating the multi-arch image manifest for ${IMAGE_REPO}/${IMAGE_NAME}:latest..." 67 | ${CONTAINER_CLI} manifest create "${IMAGE_REPO}"/"${IMAGE_NAME}":latest \ 68 | "${IMAGE_REPO}"/"${IMAGE_NAME}"-amd64:"${VERSION}" \ 69 | "${IMAGE_REPO}"/"${IMAGE_NAME}"-ppc64le:"${VERSION}" \ 70 | "${IMAGE_REPO}"/"${IMAGE_NAME}"-s390x:"${VERSION}" 71 | 72 | # push multi-arch manifest 73 | echo "Pushing the multi-arch image manifest for ${IMAGE_REPO}/${IMAGE_NAME}:${RELEASE_VERSION}..." 74 | ${CONTAINER_CLI} manifest push "${IMAGE_REPO}"/"${IMAGE_NAME}":"${RELEASE_VERSION}" 75 | echo "Pushing the multi-arch image manifest for ${IMAGE_REPO}/${IMAGE_NAME}:latest..." 76 | ${CONTAINER_CLI} manifest push "${IMAGE_REPO}"/"${IMAGE_NAME}":latest 77 | -------------------------------------------------------------------------------- /config/crd/bases/oidc.security.ibm.com_clients.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | labels: 6 | app.kubernetes.io/instance: ibm-iam-operator 7 | app.kubernetes.io/managed-by: ibm-iam-operator 8 | app.kubernetes.io/name: ibm-iam-operator 9 | name: clients.oidc.security.ibm.com 10 | spec: 11 | conversion: 12 | strategy: None 13 | group: oidc.security.ibm.com 14 | names: 15 | kind: Client 16 | listKind: ClientList 17 | plural: clients 18 | singular: client 19 | preserveUnknownFields: false 20 | scope: Namespaced 21 | versions: 22 | - name: v1 23 | schema: 24 | openAPIV3Schema: 25 | description: The Client custom resource is used for registering clients with Identity Management service 26 | properties: 27 | apiVersion: 28 | 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/api-conventions.md#resources' 29 | type: string 30 | kind: 31 | 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/api-conventions.md#types-kinds' 32 | type: string 33 | metadata: 34 | type: object 35 | spec: 36 | type: object 37 | x-kubernetes-preserve-unknown-fields: true 38 | status: 39 | type: object 40 | x-kubernetes-preserve-unknown-fields: true 41 | type: object 42 | served: true 43 | storage: true 44 | subresources: 45 | status: {} 46 | status: 47 | acceptedNames: 48 | kind: Client 49 | listKind: ClientList 50 | plural: clients 51 | singular: client 52 | conditions: [] 53 | storedVersions: 54 | - v1 55 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/oidc.security.ibm.com_clients.yaml 6 | - bases/operator.ibm.com_authentications.yaml 7 | #+kubebuilder:scaffold:crdkustomizeresource 8 | 9 | patchesStrategicMerge: 10 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 11 | # patches here are for enabling the conversion webhook for each CRD 12 | #- patches/webhook_in_clients.yaml 13 | #- patches/webhook_in_authentications.yaml 14 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 15 | 16 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 17 | # patches here are for enabling the CA injection for each CRD 18 | #- patches/cainjection_in_clients.yaml 19 | #- patches/cainjection_in_authentications.yaml 20 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 21 | 22 | # the following config is for teaching kustomize how to do kustomization for CRDs. 23 | configurations: 24 | - kustomizeconfig.yaml 25 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_oidc.security_clients.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: clients.oidc.security.ibm.com 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_operator_authentications.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: authentications.operator.ibm.com 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_oidc.security_clients.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: clients.oidc.security.ibm.com 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_operator_authentications.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: authentications.operator.ibm.com 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | affinity: 12 | nodeAffinity: 13 | requiredDuringSchedulingIgnoredDuringExecution: 14 | nodeSelectorTerms: 15 | - matchExpressions: 16 | - key: kubernetes.io/arch 17 | operator: In 18 | values: 19 | - amd64 20 | - arm64 21 | - ppc64le 22 | - s390x 23 | - key: kubernetes.io/os 24 | operator: In 25 | values: 26 | - linux 27 | containers: 28 | - name: kube-rbac-proxy 29 | securityContext: 30 | allowPrivilegeEscalation: false 31 | capabilities: 32 | drop: 33 | - "ALL" 34 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.1 35 | args: 36 | - "--secure-listen-address=0.0.0.0:8443" 37 | - "--upstream=http://127.0.0.1:8080/" 38 | - "--logtostderr=true" 39 | - "--v=0" 40 | ports: 41 | - containerPort: 8443 42 | protocol: TCP 43 | name: https 44 | resources: 45 | limits: 46 | cpu: 500m 47 | memory: 128Mi 48 | requests: 49 | cpu: 5m 50 | memory: 64Mi 51 | - name: manager 52 | args: 53 | - "--health-probe-bind-address=:8081" 54 | - "--metrics-bind-address=127.0.0.1:8080" 55 | - "--leader-elect" 56 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | -------------------------------------------------------------------------------- /config/default/overlays/prod/kustomization.yaml: -------------------------------------------------------------------------------- 1 | bases: 2 | - ../../../crd 3 | - ../../../rbac 4 | - ../../../manager/overlays/prod 5 | -------------------------------------------------------------------------------- /config/manager/bases/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | -------------------------------------------------------------------------------- /config/manager/bases/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: ibm-iam-operator 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: ibm-iam-operator 12 | labels: 13 | app.kubernetes.io/instance: ibm-iam-operator 14 | app.kubernetes.io/managed-by: ibm-iam-operator 15 | app.kubernetes.io/name: ibm-iam-operator 16 | productName: IBM_Cloud_Platform_Common_Services 17 | spec: 18 | replicas: 1 19 | selector: 20 | matchLabels: 21 | name: ibm-iam-operator 22 | template: 23 | metadata: 24 | labels: 25 | name: ibm-iam-operator 26 | app.kubernetes.io/instance: ibm-iam-operator 27 | app.kubernetes.io/managed-by: ibm-iam-operator 28 | app.kubernetes.io/name: ibm-iam-operator 29 | intent: projected 30 | productName: IBM_Cloud_Platform_Common_Services 31 | annotations: 32 | productName: IBM Cloud Platform Common Services 33 | productID: "068a62892a1e4db39641342e592daa25" 34 | productMetric: FREE 35 | spec: 36 | serviceAccountName: ibm-iam-operator 37 | securityContext: 38 | seccompProfile: 39 | type: RuntimeDefault 40 | affinity: 41 | securityContext: 42 | seccompProfile: 43 | type: RuntimeDefault 44 | nodeAffinity: 45 | requiredDuringSchedulingIgnoredDuringExecution: 46 | nodeSelectorTerms: 47 | - matchExpressions: 48 | - key: kubernetes.io/arch 49 | operator: In 50 | values: 51 | - amd64 52 | - ppc64le 53 | - s390x 54 | containers: 55 | - name: ibm-iam-operator 56 | image: controller:latest 57 | command: 58 | - ibm-iam-operator 59 | imagePullPolicy: IfNotPresent 60 | env: 61 | - name: WATCH_NAMESPACE 62 | valueFrom: 63 | configMapKeyRef: 64 | name: namespace-scope 65 | key: namespaces 66 | - name: POD_NAME 67 | valueFrom: 68 | fieldRef: 69 | fieldPath: metadata.name 70 | - name: POD_NAMESPACE 71 | valueFrom: 72 | fieldRef: 73 | fieldPath: metadata.namespace 74 | - name: OPERATOR_NAME 75 | value: "ibm-iam-operator" 76 | - name: ROUTE_HTTP_PORT 77 | value: "" 78 | - name: ROUTE_HTTPS_PORT 79 | value: "" 80 | - name: cluster_name 81 | value: "" 82 | resources: 83 | limits: 84 | cpu: 25m 85 | memory: 320Mi 86 | requests: 87 | cpu: 20m 88 | memory: 80Mi 89 | ephemeral-storage: 256Mi 90 | securityContext: 91 | seccompProfile: 92 | type: RuntimeDefault 93 | allowPrivilegeEscalation: false 94 | capabilities: 95 | drop: 96 | - ALL 97 | privileged: false 98 | readOnlyRootFilesystem: true 99 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases 3 | -------------------------------------------------------------------------------- /config/manager/overlays/prod/image_env_vars_patch.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /spec/template/spec/containers/0/env/- 3 | value: 4 | name: ICP_PLATFORM_AUTH_IMAGE 5 | value: icr.io/cpopen/cpfs/icp-platform-auth:4.12.0 6 | - op: add 7 | path: /spec/template/spec/containers/0/env/- 8 | value: 9 | name: ICP_IDENTITY_PROVIDER_IMAGE 10 | value: icr.io/cpopen/cpfs/icp-identity-provider:4.12.0 11 | - op: add 12 | path: /spec/template/spec/containers/0/env/- 13 | value: 14 | name: ICP_IDENTITY_MANAGER_IMAGE 15 | value: icr.io/cpopen/cpfs/icp-identity-manager:4.12.0 16 | - op: add 17 | path: /spec/template/spec/containers/0/env/- 18 | value: 19 | name: IM_INITCONTAINER_IMAGE 20 | value: icr.io/cpopen/cpfs/im-initcontainer:4.12.0 21 | - op: replace 22 | path: /spec/template/spec/containers/0/imagePullPolicy 23 | value: IfNotPresent 24 | -------------------------------------------------------------------------------- /config/manager/overlays/prod/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ../../bases 3 | generatorOptions: 4 | disableNameSuffixHash: true 5 | apiVersion: kustomize.config.k8s.io/v1beta1 6 | kind: Kustomization 7 | images: 8 | - name: controller 9 | newName: icr.io/cpopen/ibm-iam-operator 10 | newTag: 4.12.0 11 | patches: 12 | - path: ./image_env_vars_patch.yaml 13 | target: 14 | group: apps 15 | version: v1 16 | kind: Deployment 17 | name: ibm-iam-operator 18 | -------------------------------------------------------------------------------- /config/manifests/bases/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ibm-iam-operator.clusterserviceversion.yaml 3 | -------------------------------------------------------------------------------- /config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases 3 | - ../default 4 | - ../samples 5 | - ../scorecard 6 | -------------------------------------------------------------------------------- /config/manifests/overlays/prod/annotations_patch.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: "/metadata/annotations/olm.skipRange" 3 | value: <4.12.0 4 | - op: add 5 | path: "/metadata/annotations/containerImage" 6 | value: icr.io/cpopen/ibm-iam-operator:4.12.0 7 | -------------------------------------------------------------------------------- /config/manifests/overlays/prod/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ../../bases 3 | - ../../../default/overlays/prod 4 | - ../../../samples/overlays/prod 5 | - ../../../scorecard 6 | 7 | patches: 8 | - path: annotations_patch.yaml 9 | target: 10 | group: operators.coreos.com 11 | version: v1alpha1 12 | kind: ClusterServiceVersion 13 | name: ibm-iam-operator.v0.0.0 14 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | app.kubernetes.io/name: servicemonitor 9 | app.kubernetes.io/instance: controller-manager-metrics-monitor 10 | app.kubernetes.io/component: metrics 11 | app.kubernetes.io/created-by: ibm-iam-operator 12 | app.kubernetes.io/part-of: ibm-iam-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | name: controller-manager-metrics-monitor 15 | namespace: system 16 | spec: 17 | endpoints: 18 | - path: /metrics 19 | port: https 20 | scheme: https 21 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 22 | tlsConfig: 23 | insecureSkipVerify: true 24 | selector: 25 | matchLabels: 26 | control-plane: controller-manager 27 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: metrics-reader 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: ibm-iam-operator 9 | app.kubernetes.io/part-of: ibm-iam-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: metrics-reader 12 | rules: 13 | - nonResourceURLs: 14 | - "/metrics" 15 | verbs: 16 | - get 17 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: proxy-role 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: ibm-iam-operator 9 | app.kubernetes.io/part-of: ibm-iam-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-role 12 | rules: 13 | - apiGroups: 14 | - authentication.k8s.io 15 | resources: 16 | - tokenreviews 17 | verbs: 18 | - create 19 | - apiGroups: 20 | - authorization.k8s.io 21 | resources: 22 | - subjectaccessreviews 23 | verbs: 24 | - create 25 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: proxy-rolebinding 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: ibm-iam-operator 9 | app.kubernetes.io/part-of: ibm-iam-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: proxy-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: service 7 | app.kubernetes.io/instance: controller-manager-metrics-service 8 | app.kubernetes.io/component: kube-rbac-proxy 9 | app.kubernetes.io/created-by: ibm-iam-operator 10 | app.kubernetes.io/part-of: ibm-iam-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: controller-manager-metrics-service 13 | namespace: system 14 | spec: 15 | ports: 16 | - name: https 17 | port: 8443 18 | protocol: TCP 19 | targetPort: https 20 | selector: 21 | control-plane: controller-manager 22 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | # Comment the following 4 lines if you want to disable 11 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 12 | # which protects your /metrics endpoint. 13 | #- auth_proxy_service.yaml 14 | #- auth_proxy_role.yaml 15 | #- auth_proxy_role_binding.yaml 16 | #- auth_proxy_client_clusterrole.yaml 17 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: role 7 | app.kubernetes.io/instance: leader-election-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: ibm-iam-operator 10 | app.kubernetes.io/part-of: ibm-iam-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: leader-election-role 13 | rules: 14 | - apiGroups: 15 | - "" 16 | resources: 17 | - configmaps 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - create 23 | - update 24 | - patch 25 | - delete 26 | - apiGroups: 27 | - coordination.k8s.io 28 | resources: 29 | - leases 30 | verbs: 31 | - get 32 | - list 33 | - watch 34 | - create 35 | - update 36 | - patch 37 | - delete 38 | - apiGroups: 39 | - "" 40 | resources: 41 | - events 42 | verbs: 43 | - create 44 | - patch 45 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: rolebinding 6 | app.kubernetes.io/instance: leader-election-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: ibm-iam-operator 9 | app.kubernetes.io/part-of: ibm-iam-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: leader-election-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: Role 15 | name: leader-election-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/oidc.security_client_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit clients. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: client-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: ibm-iam-operator 10 | app.kubernetes.io/part-of: ibm-iam-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: client-editor-role 13 | rules: 14 | - apiGroups: 15 | - oidc.security.ibm.com 16 | resources: 17 | - clients 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - oidc.security.ibm.com 28 | resources: 29 | - clients/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/oidc.security_client_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view clients. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: client-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: ibm-iam-operator 10 | app.kubernetes.io/part-of: ibm-iam-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: client-viewer-role 13 | rules: 14 | - apiGroups: 15 | - oidc.security.ibm.com 16 | resources: 17 | - clients 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - oidc.security.ibm.com 24 | resources: 25 | - clients/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/operator_authentication_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit authentications. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: authentication-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: ibm-iam-operator 10 | app.kubernetes.io/part-of: ibm-iam-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: authentication-editor-role 13 | rules: 14 | - apiGroups: 15 | - operator.ibm.com 16 | resources: 17 | - authentications 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - operator.ibm.com 28 | resources: 29 | - authentications/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/operator_authentication_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view authentications. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: authentication-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: ibm-iam-operator 10 | app.kubernetes.io/part-of: ibm-iam-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: authentication-viewer-role 13 | rules: 14 | - apiGroups: 15 | - operator.ibm.com 16 | resources: 17 | - authentications 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - operator.ibm.com 24 | resources: 25 | - authentications/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | creationTimestamp: null 5 | name: ibm-iam-operator 6 | labels: 7 | app.kubernetes.io/instance: ibm-iam-operator 8 | app.kubernetes.io/managed-by: ibm-iam-operator 9 | app.kubernetes.io/name: ibm-iam-operator 10 | rules: 11 | - apiGroups: 12 | - route.openshift.io 13 | resources: 14 | - routes 15 | verbs: 16 | - get 17 | - list 18 | - watch 19 | - create 20 | - delete 21 | - update 22 | - patch 23 | - apiGroups: 24 | - route.openshift.io 25 | resources: 26 | - routes/custom-host 27 | verbs: 28 | - create 29 | - apiGroups: 30 | - route.openshift.io 31 | resources: 32 | - routes/status 33 | verbs: 34 | - get 35 | - list 36 | - watch 37 | - apiGroups: 38 | - "" 39 | resources: 40 | - pods 41 | - services 42 | - services/finalizers 43 | - endpoints 44 | - persistentvolumeclaims 45 | - events 46 | - configmaps 47 | - secrets 48 | verbs: 49 | - create 50 | - delete 51 | - get 52 | - list 53 | - patch 54 | - update 55 | - watch 56 | - apiGroups: 57 | - apps 58 | resources: 59 | - deployments 60 | - daemonsets 61 | - replicasets 62 | - statefulsets 63 | verbs: 64 | - create 65 | - delete 66 | - get 67 | - list 68 | - patch 69 | - update 70 | - watch 71 | - apiGroups: 72 | - oidc.security.ibm.com 73 | resources: 74 | - clients 75 | - clients/finalizers 76 | - clients/status 77 | verbs: 78 | - create 79 | - delete 80 | - get 81 | - list 82 | - patch 83 | - update 84 | - watch 85 | - apiGroups: 86 | - monitoring.coreos.com 87 | resources: 88 | - servicemonitors 89 | verbs: 90 | - get 91 | - create 92 | - apiGroups: 93 | - apps 94 | resourceNames: 95 | - ibm-iam-operator 96 | resources: 97 | - deployments/finalizers 98 | verbs: 99 | - update 100 | - apiGroups: 101 | - "" 102 | resources: 103 | - pods 104 | verbs: 105 | - get 106 | - apiGroups: 107 | - apps 108 | resources: 109 | - replicasets 110 | - deployments 111 | verbs: 112 | - get 113 | - apiGroups: 114 | - operator.ibm.com 115 | resources: 116 | - authentications 117 | verbs: 118 | - create 119 | - delete 120 | - get 121 | - list 122 | - patch 123 | - update 124 | - watch 125 | - apiGroups: 126 | - operator.ibm.com 127 | resources: 128 | - authentications/finalizers 129 | verbs: 130 | - update 131 | - patch 132 | - apiGroups: 133 | - operator.ibm.com 134 | resources: 135 | - authentications/status 136 | verbs: 137 | - get 138 | - patch 139 | - update 140 | - apiGroups: 141 | - operator.ibm.com 142 | resources: 143 | - commonservices 144 | verbs: 145 | - get 146 | - list 147 | - create 148 | - apiGroups: 149 | - operator.ibm.com 150 | resources: 151 | - operandrequests 152 | - operandbindinfos 153 | verbs: 154 | - create 155 | - get 156 | - list 157 | - patch 158 | - watch 159 | - update 160 | - delete 161 | - apiGroups: 162 | - operator.ibm.com 163 | resources: 164 | - operandrequests/status 165 | - operandbindinfos/status 166 | verbs: 167 | - watch 168 | - get 169 | - list 170 | - apiGroups: 171 | - cert-manager.io 172 | resources: 173 | - certificates 174 | - certificaterequests 175 | - orders 176 | - challenges 177 | - issuers 178 | verbs: 179 | - create 180 | - delete 181 | - get 182 | - list 183 | - patch 184 | - update 185 | - watch 186 | - apiGroups: 187 | - certmanager.k8s.io 188 | resources: 189 | - certificates 190 | verbs: 191 | - delete 192 | - get 193 | - list 194 | - watch 195 | - apiGroups: 196 | - networking.k8s.io 197 | resources: 198 | - ingresses 199 | verbs: 200 | - create 201 | - delete 202 | - get 203 | - list 204 | - patch 205 | - update 206 | - watch 207 | - apiGroups: 208 | - batch 209 | resources: 210 | - jobs 211 | verbs: 212 | - create 213 | - delete 214 | - get 215 | - list 216 | - patch 217 | - update 218 | - watch 219 | - apiGroups: 220 | - "" 221 | resources: 222 | - serviceaccounts 223 | verbs: 224 | - create 225 | - get 226 | - list 227 | - patch 228 | - update 229 | - watch 230 | - apiGroups: 231 | - rbac.authorization.k8s.io 232 | resources: 233 | - roles 234 | - rolebindings 235 | verbs: 236 | - create 237 | - apiGroups: 238 | - zen.cpd.ibm.com 239 | resources: 240 | - zenextensions 241 | verbs: 242 | - create 243 | - delete 244 | - get 245 | - list 246 | - patch 247 | - update 248 | - watch 249 | - apiGroups: 250 | - autoscaling 251 | resources: 252 | - horizontalpodautoscalers 253 | verbs: 254 | - create 255 | - delete 256 | - get 257 | - list 258 | - patch 259 | - update 260 | - watch 261 | --- 262 | apiVersion: rbac.authorization.k8s.io/v1 263 | kind: ClusterRole 264 | metadata: 265 | name: ibm-iam-operator 266 | labels: 267 | app.kubernetes.io/instance: ibm-iam-operator 268 | app.kubernetes.io/managed-by: ibm-iam-operator 269 | app.kubernetes.io/name: ibm-iam-operator 270 | rules: 271 | - apiGroups: 272 | - rbac.authorization.k8s.io 273 | resources: 274 | - clusterroles 275 | - clusterrolebindings 276 | verbs: 277 | - create 278 | - apiGroups: 279 | - user.openshift.io 280 | resources: 281 | - users 282 | - groups 283 | - identities 284 | verbs: 285 | - get 286 | - list 287 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | kind: RoleBinding 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: ibm-iam-operator 5 | labels: 6 | app.kubernetes.io/instance: ibm-iam-operator 7 | app.kubernetes.io/managed-by: ibm-iam-operator 8 | app.kubernetes.io/name: ibm-iam-operator 9 | subjects: 10 | - kind: ServiceAccount 11 | name: ibm-iam-operator 12 | roleRef: 13 | kind: Role 14 | name: ibm-iam-operator 15 | apiGroup: rbac.authorization.k8s.io 16 | 17 | --- 18 | apiVersion: rbac.authorization.k8s.io/v1 19 | kind: ClusterRoleBinding 20 | metadata: 21 | name: ibm-iam-operator 22 | subjects: 23 | - kind: ServiceAccount 24 | name: ibm-iam-operator 25 | namespace: ibm-common-services 26 | roleRef: 27 | kind: ClusterRole 28 | name: ibm-iam-operator 29 | apiGroup: rbac.authorization.k8s.io 30 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: ibm-iam-operator 5 | labels: 6 | app.kubernetes.io/instance: ibm-iam-operator 7 | app.kubernetes.io/managed-by: ibm-iam-operator 8 | app.kubernetes.io/name: ibm-iam-operator 9 | -------------------------------------------------------------------------------- /config/samples/bases/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - operator_v1alpha1_authentication.yaml 3 | -------------------------------------------------------------------------------- /config/samples/bases/oidc.security_v1_client.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: oidc.security.ibm.com/v1 2 | kind: Client 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: client 6 | app.kubernetes.io/instance: client-sample 7 | app.kubernetes.io/part-of: ibm-iam-operator-sdkv1 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: ibm-iam-operator-sdkv1 10 | name: client-sample 11 | spec: 12 | # TODO(user): Add fields here 13 | -------------------------------------------------------------------------------- /config/samples/bases/operator_v1alpha1_authentication.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: operator.ibm.com/v1alpha1 3 | kind: Authentication 4 | metadata: 5 | labels: 6 | app.kubernetes.io/instance: ibm-iam-operator 7 | app.kubernetes.io/managed-by: ibm-iam-operator 8 | app.kubernetes.io/name: ibm-iam-operator 9 | name: example-authentication 10 | spec: 11 | auditService: 12 | imageName: auditService no longer used - ignore 13 | imageRegistry: auditService no longer used - ignore 14 | imageTag: auditService no longer used - ignore 15 | authService: 16 | imageName: icp-platform-auth 17 | imageRegistry: quay.io/opencloudio 18 | imageTag: 0.0.0 19 | ldapsCACert: platform-auth-ldaps-ca-cert 20 | resources: 21 | limits: 22 | cpu: 1000m 23 | memory: 1Gi 24 | ephemeral-storage: 650Mi 25 | requests: 26 | cpu: 100m 27 | memory: 350Mi 28 | ephemeral-storage: 400Mi 29 | routerCertSecret: saml-auth-secret 30 | clientRegistration: 31 | imageName: im-initcontainer 32 | imageRegistry: quay.io/opencloudio 33 | imageTag: 0.0.0 34 | resources: 35 | limits: 36 | cpu: 1000m 37 | memory: 1Gi 38 | requests: 39 | cpu: 100m 40 | memory: 128Mi 41 | ephemeral-storage: 158Mi 42 | config: 43 | authUniqueHosts: internal-ip1 internal-ip2 mycluster.icp 44 | clusterCADomain: mycluster.icp 45 | clusterExternalAddress: 10.0.0.1 46 | clusterInternalAddress: 10.0.0.1 47 | clusterName: mycluster 48 | defaultAdminPassword: password 49 | defaultAdminUser: cpadmin 50 | scimAdminPassword: scimpassword 51 | scimAdminUser: scimadmin 52 | enableImpersonation: false 53 | fipsEnabled: true 54 | ibmCloudSaas: false 55 | icpPort: 8443 56 | installType: fresh 57 | isOpenshiftEnv: true 58 | nonceEnabled: true 59 | xframeDomain: '' 60 | zenFrontDoor: false 61 | preferredLogin: '' 62 | defaultLogin: '' 63 | bootstrapUserId: kubeadmin 64 | providerIssuerURL: '' 65 | claimsSupported: name,family_name,display_name,given_name,preferred_username 66 | claimsMap: name="givenName" family_name="givenName" given_name="givenName" preferred_username="displayName" 67 | display_name="displayName" 68 | scopeClaim: profile="name,family_name,display_name,given_name,preferred_username" 69 | oidcIssuerURL: https://127.0.0.1:443/idauth/oidc/endpoint/OP 70 | openshiftPort: 443 71 | roksEnabled: true 72 | roksURL: https://roks.domain.name:443 73 | roksUserPrefix: changeme 74 | saasClientRedirectUrl: '' 75 | wlpClientID: 4444be3a738841016ab76d71b650e836 76 | wlpClientRegistrationSecret: f1362ca4d20b8389af2d1ea68042c9af 77 | wlpClientSecret: aa73bf39752053bf723d1143fb4cf8a2 78 | identityManager: 79 | imageName: icp-identity-manager 80 | imageRegistry: quay.io/opencloudio 81 | imageTag: 0.0.0 82 | masterNodesList: 10.0.0.1 83 | resources: 84 | limits: 85 | cpu: 1000m 86 | memory: 1Gi 87 | ephemeral-storage: 550Mi 88 | requests: 89 | cpu: 50m 90 | memory: 150Mi 91 | ephemeral-storage: 300Mi 92 | identityProvider: 93 | imageName: icp-identity-provider 94 | imageRegistry: quay.io/opencloudio 95 | imageTag: 0.0.0 96 | resources: 97 | limits: 98 | cpu: 1000m 99 | memory: 1Gi 100 | ephemeral-storage: 550Mi 101 | requests: 102 | cpu: 50m 103 | memory: 150Mi 104 | ephemeral-storage: 300Mi 105 | initMongodb: 106 | imageName: im-initcontainer 107 | imageRegistry: quay.io/opencloudio 108 | imageTag: 0.0.0 109 | resources: 110 | limits: 111 | cpu: 100m 112 | memory: 128Mi 113 | requests: 114 | cpu: 100m 115 | memory: 128Mi 116 | ephemeral-storage: 178Mi 117 | operatorVersion: 0.14.1 118 | replicas: 1 119 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - bases 4 | #+kubebuilder:scaffold:manifestskustomizesamples 5 | -------------------------------------------------------------------------------- /config/samples/overlays/prod/authentication_image_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: operator.ibm.com/v1alpha1 2 | kind: Authentication 3 | metadata: 4 | name: example-authentication 5 | spec: 6 | authService: 7 | imageRegistry: icr.io/cpopen/cpfs 8 | imageTag: 4.12.0 9 | clientRegistration: 10 | imageRegistry: icr.io/cpopen/cpfs 11 | imageTag: 4.12.0 12 | identityManager: 13 | imageRegistry: icr.io/cpopen/cpfs 14 | imageTag: 4.12.0 15 | identityProvider: 16 | imageRegistry: icr.io/cpopen/cpfs 17 | imageTag: 4.12.0 18 | initMongodb: 19 | imageRegistry: icr.io/cpopen/cpfs 20 | imageTag: 4.12.0 21 | -------------------------------------------------------------------------------- /config/samples/overlays/prod/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - ../../bases 4 | #+kubebuilder:scaffold:manifestskustomizesamples 5 | 6 | patchesStrategicMerge: 7 | - authentication_image_patch.yaml 8 | -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | #+kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.29.0 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.29.0 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.29.0 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.29.0 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.29.0 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.29.0 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /controllers/common/constants.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package common 18 | 19 | const GlobalConfigMapName string = "ibm-cpp-config" 20 | const CommonServiceName string = "common-service" 21 | 22 | // Name of ConfigMap that configures IBM Cloud cluster information 23 | const IBMCloudClusterInfoCMName string = "ibmcloud-cluster-info" 24 | 25 | // Name of ConfigMap that configures external or embedded EDB for IM 26 | const DatastoreEDBCMName string = "im-datastore-edb-cm" 27 | 28 | // Name of Secret containing certificates for connecting to EDB 29 | const DatastoreEDBSecretName string = "im-datastore-edb-secret" 30 | 31 | // Name of the mongodb operator deployment name 32 | const MongoOprDeploymentName string = "ibm-mongodb-operator" 33 | 34 | // Name of the mongodb statefulset name 35 | const MongoStatefulsetName string = "icp-mongodb" 36 | 37 | // Name of CommonService created by IM Operator to provision EDB share 38 | const DatastoreEDBCSName string = "im-common-service" 39 | 40 | type DeploymentName string 41 | 42 | // The current names of Deployments managed by this Operator 43 | const ( 44 | PlatformIdentityProvider DeploymentName = "platform-identity-provider" 45 | PlatformIdentityManagement DeploymentName = "platform-identity-management" 46 | PlatformAuthService DeploymentName = "platform-auth-service" 47 | ) 48 | 49 | const ManagerVersionLabel string = "authentication.operator.ibm.com/manager-version" 50 | -------------------------------------------------------------------------------- /controllers/common/randomstring.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package common 18 | 19 | import ( 20 | "math/rand" 21 | "time" 22 | 23 | regen "github.com/zach-klippenstein/goregen" 24 | ) 25 | 26 | // GenerateRandomString generates a random string based upon a string that is a valid regex pattern. 27 | func GenerateRandomString(rule string) string { 28 | 29 | generator, _ := regen.NewGenerator(rule, ®en.GeneratorArgs{ 30 | RngSource: rand.NewSource(time.Now().UnixNano()), 31 | MaxUnboundedRepeatCount: 1}) 32 | randomString := generator.Generate() 33 | return randomString 34 | } 35 | -------------------------------------------------------------------------------- /controllers/common/resources.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package common 18 | 19 | import ( 20 | "regexp" 21 | 22 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | "sigs.k8s.io/controller-runtime/pkg/client" 25 | "sigs.k8s.io/controller-runtime/pkg/client/apiutil" 26 | ) 27 | 28 | var CsConfigAnnotationSuffix = "common-service/config" 29 | var CsDefaultNamespace = "ibm-common-services" 30 | 31 | // GetCsConfigAnnotation returns '.common-service/config' annotation name for given namespace 32 | func GetCsConfigAnnotation(namespace string) string { 33 | if len(namespace) == 0 { 34 | return CsDefaultNamespace + "." + CsConfigAnnotationSuffix 35 | } 36 | 37 | return namespace + "." + CsConfigAnnotationSuffix 38 | } 39 | 40 | // IsCsConfigAnnotationExists checks if '.common-service/config' annotation name exists in the given annotations map or not 41 | func IsCsConfigAnnotationExists(annotations map[string]string) bool { 42 | if len(annotations) == 0 { 43 | return false 44 | } 45 | csAnnotationFound := false 46 | reg, _ := regexp.Compile(`^(.*)\.common-service\/config`) 47 | for anno := range annotations { 48 | if reg.MatchString(anno) { 49 | csAnnotationFound = true 50 | break 51 | } 52 | } 53 | return csAnnotationFound 54 | } 55 | 56 | func isOwnerOf(owner client.Object, ownerRef v1.OwnerReference) (isOwner bool) { 57 | ownerGVK := owner.GetObjectKind().GroupVersionKind() 58 | if ownerRef.Kind == ownerGVK.Kind && ownerRef.UID == owner.GetUID() && ownerRef.Name == owner.GetName() && ownerRef.APIVersion == ownerGVK.GroupVersion().String() { 59 | return true 60 | } 61 | return 62 | } 63 | 64 | func isControllerOf(controller client.Object, ownerRef v1.OwnerReference) (isController bool) { 65 | if isOwnerOf(controller, ownerRef) && *ownerRef.Controller { 66 | return true 67 | } 68 | return 69 | } 70 | 71 | // IsOwnerOf determines whether one object is listed in another object's OwnerReferences. 72 | func IsOwnerOf(scheme *runtime.Scheme, owner, owned client.Object) (isOwner bool) { 73 | ownerRefs := owned.GetOwnerReferences() 74 | if len(ownerRefs) == 0 { 75 | return 76 | } 77 | gvk, err := apiutil.GVKForObject(owner, scheme) 78 | if err != nil { 79 | return 80 | } 81 | owner.GetObjectKind().SetGroupVersionKind(gvk) 82 | for _, ownerRef := range ownerRefs { 83 | if isOwnerOf(owner, ownerRef) { 84 | return true 85 | } 86 | } 87 | return 88 | } 89 | 90 | // IsControllerOf determines whether one object is listed as the controller of another object within its 91 | // OwnerReferences. 92 | func IsControllerOf(scheme *runtime.Scheme, controller, controlled client.Object) (isController bool) { 93 | ownerRefs := controlled.GetOwnerReferences() 94 | if len(ownerRefs) == 0 { 95 | return 96 | } 97 | gvk, err := apiutil.GVKForObject(controller, scheme) 98 | if err != nil { 99 | return 100 | } 101 | controller.GetObjectKind().SetGroupVersionKind(gvk) 102 | for _, ownerRef := range ownerRefs { 103 | if isControllerOf(controller, ownerRef) { 104 | return true 105 | } 106 | } 107 | return 108 | } 109 | 110 | func GetControllerKind(controlled client.Object) (kind string) { 111 | index := GetControllerRefIndex(controlled) 112 | if index == -1 { 113 | return 114 | } 115 | return controlled.GetOwnerReferences()[index].Kind 116 | } 117 | 118 | func GetControllerRefIndex(controlled client.Object) (index int) { 119 | index = -1 120 | ownerRefs := controlled.GetOwnerReferences() 121 | if len(ownerRefs) == 0 { 122 | return 123 | } 124 | 125 | for i, ownerRef := range ownerRefs { 126 | if *ownerRef.Controller { 127 | return i 128 | } 129 | } 130 | return 131 | } 132 | -------------------------------------------------------------------------------- /controllers/common/shatag.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020, 2021 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package common 18 | 19 | import ( 20 | "os" 21 | ) 22 | 23 | func GetImageRef(envVar string) string { 24 | 25 | imageName := os.Getenv(envVar) 26 | 27 | return imageName 28 | } 29 | -------------------------------------------------------------------------------- /controllers/common/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package common 18 | 19 | import ( 20 | "testing" 21 | 22 | . "github.com/onsi/ginkgo/v2" 23 | . "github.com/onsi/gomega" 24 | 25 | logf "sigs.k8s.io/controller-runtime/pkg/log" 26 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 27 | //+kubebuilder:scaffold:imports 28 | ) 29 | 30 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 31 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 32 | 33 | func TestAPIs(t *testing.T) { 34 | RegisterFailHandler(Fail) 35 | 36 | RunSpecs(t, "Common Suite") 37 | } 38 | 39 | var _ = BeforeSuite(func() { 40 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 41 | By("bootstrapping test environment") 42 | }) 43 | 44 | var _ = AfterSuite(func() { 45 | By("tearing down the test environment") 46 | }) 47 | -------------------------------------------------------------------------------- /controllers/common/utils_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2025 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package common 18 | 19 | import ( 20 | . "github.com/onsi/ginkgo/v2" 21 | . "github.com/onsi/gomega" 22 | corev1 "k8s.io/api/core/v1" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | "sigs.k8s.io/controller-runtime/pkg/client" 25 | fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" 26 | ) 27 | 28 | var _ = Describe("objectTable", func() { 29 | var cb fakeclient.ClientBuilder 30 | var cl client.WithWatch 31 | var scheme *runtime.Scheme 32 | BeforeEach(func() { 33 | scheme = runtime.NewScheme() 34 | Expect(corev1.AddToScheme(scheme)).To(Succeed()) 35 | cb = *fakeclient.NewClientBuilder(). 36 | WithScheme(scheme) 37 | cl = cb.Build() 38 | }) 39 | // Reminder: closures necessary if using Before* with DescribeTable 40 | DescribeTable("GetEmptyObject", 41 | func(build func() Secondary, obj client.Object) { 42 | s := build() 43 | o := s.GetEmptyObject() 44 | Expect(o).To(Equal(obj)) 45 | Expect(o).ToNot(BeNil()) 46 | }, 47 | Entry("*corev1.Secret", func() Secondary { return NewSecondaryReconcilerBuilder[*corev1.Secret]().WithClient(cl).MustBuild() }, &corev1.Secret{})) 48 | }) 49 | -------------------------------------------------------------------------------- /controllers/oidc.security/conditions.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package oidcsecurity 18 | 19 | import ( 20 | "context" 21 | "net/http" 22 | 23 | oidcsecurityv1 "github.com/IBM/ibm-iam-operator/apis/oidc.security/v1" 24 | corev1 "k8s.io/api/core/v1" 25 | "k8s.io/apimachinery/pkg/api/meta" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | ) 28 | 29 | const ( 30 | MessageCreateClientSuccessful = "OIDC client registration create successful" 31 | MessageUpdateClientSuccessful = "OIDC client registration update successful" 32 | MessageClientSuccessful = "OIDC client registration successful" 33 | MessageCreateClientFailed = "OIDC client registration create failed" 34 | MessageCreateZenRegistrationFailed = "Registration of the Zen Instance failed" 35 | MessageUnknown string = "Unexpected error occurred while processing the request" 36 | 37 | ReasonCreateClientSuccessful = "CreateClientSuccessful" 38 | ReasonCreateClientFailed = "CreateClientFailed" 39 | ReasonUpdateClientSuccessful = "UpdateClientSuccessful" 40 | ReasonUpdateClientFailed = "UpdateClientFailed" 41 | ReasonGetClientFailed = "GetClientFailed" 42 | ReasonDeleteClientFailed = "DeleteClientFailed" 43 | ReasonCreateZenRegistrationFailed = "CreateZenRegistrationFailed" 44 | ReasonUnknown string = "Unknown" 45 | ) 46 | 47 | // writeErrorConditionsAndEvents updates a Client CR's`.status.conditions` and writes an event related to the outcome. 48 | // It is a no-op if err is nil. 49 | func (r *ClientReconciler) writeErrorConditionsAndEvents(ctx context.Context, clientCR *oidcsecurityv1.Client, err error, requestMethod string) (statusUpdateErr error) { 50 | //reqLogger := logf.FromContext(ctx).WithValues("clientId", clientCR.Spec.ClientId, "namespace", clientCR.Namespace) 51 | var condition metav1.Condition 52 | if err == nil { 53 | return 54 | } 55 | 56 | // If the error doesn't relate to a failure of or transmitted by HTTP, this isn't a OIDC or Zen registration 57 | // problem 58 | httpErr, ok := err.(httpTyped) 59 | if !ok { 60 | condition = metav1.Condition{ 61 | Type: oidcsecurityv1.ClientConditionReady, 62 | Status: metav1.ConditionFalse, 63 | Reason: ReasonUnknown, 64 | Message: err.Error(), 65 | } 66 | meta.SetStatusCondition(&clientCR.Status.Conditions, condition) 67 | 68 | statusUpdateErr = r.Client.Status().Update(ctx, clientCR) 69 | return 70 | } 71 | 72 | // As for Zen, only report Zen client problems as registration creation issues 73 | if IsZenError(err) { 74 | condition = metav1.Condition{ 75 | Type: oidcsecurityv1.ClientConditionReady, 76 | Status: metav1.ConditionFalse, 77 | Reason: ReasonCreateZenRegistrationFailed, 78 | Message: MessageCreateZenRegistrationFailed, 79 | } 80 | meta.SetStatusCondition(&clientCR.Status.Conditions, condition) 81 | 82 | statusUpdateErr = r.Client.Status().Update(ctx, clientCR) 83 | r.Recorder.Event(clientCR, corev1.EventTypeWarning, ReasonCreateZenRegistrationFailed, err.Error()) 84 | return 85 | } 86 | 87 | var reason string 88 | switch httpErr.RequestMethod() { 89 | case http.MethodPost: 90 | reason = ReasonCreateClientFailed 91 | condition = metav1.Condition{ 92 | Type: oidcsecurityv1.ClientConditionReady, 93 | Status: metav1.ConditionFalse, 94 | Reason: reason, 95 | Message: MessageCreateClientFailed, 96 | } 97 | meta.SetStatusCondition(&clientCR.Status.Conditions, condition) 98 | 99 | statusUpdateErr = r.Client.Status().Update(ctx, clientCR) 100 | case http.MethodPut: 101 | reason = ReasonUpdateClientFailed 102 | case http.MethodGet: 103 | reason = ReasonGetClientFailed 104 | case http.MethodDelete: 105 | reason = ReasonDeleteClientFailed 106 | default: 107 | reason = ReasonUnknown 108 | } 109 | 110 | r.Recorder.Event(clientCR, corev1.EventTypeWarning, reason, err.Error()) 111 | return 112 | } 113 | -------------------------------------------------------------------------------- /controllers/oidc.security/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package oidcsecurity 18 | 19 | import ( 20 | "context" 21 | "path/filepath" 22 | "testing" 23 | 24 | . "github.com/onsi/ginkgo/v2" 25 | . "github.com/onsi/gomega" 26 | 27 | "k8s.io/client-go/kubernetes/scheme" 28 | "k8s.io/client-go/rest" 29 | "sigs.k8s.io/controller-runtime/pkg/client" 30 | "sigs.k8s.io/controller-runtime/pkg/envtest" 31 | logf "sigs.k8s.io/controller-runtime/pkg/log" 32 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 33 | 34 | oidcsecurityv1 "github.com/IBM/ibm-iam-operator/apis/oidc.security/v1" 35 | //+kubebuilder:scaffold:imports 36 | ) 37 | 38 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 39 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 40 | 41 | var ( 42 | cfg *rest.Config 43 | k8sClient client.Client 44 | testEnv *envtest.Environment 45 | ctx context.Context 46 | cancel context.CancelFunc 47 | ) 48 | 49 | func TestAPIs(t *testing.T) { 50 | RegisterFailHandler(Fail) 51 | 52 | RunSpecs(t, "Controller Suite") 53 | } 54 | 55 | var _ = BeforeSuite(func() { 56 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 57 | 58 | By("bootstrapping test environment") 59 | testEnv = &envtest.Environment{ 60 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, 61 | ErrorIfCRDPathMissing: true, 62 | } 63 | 64 | var err error 65 | // cfg is defined in this file globally. 66 | cfg, err = testEnv.Start() 67 | Expect(err).NotTo(HaveOccurred()) 68 | Expect(cfg).NotTo(BeNil()) 69 | 70 | err = oidcsecurityv1.AddToScheme(scheme.Scheme) 71 | Expect(err).NotTo(HaveOccurred()) 72 | 73 | //+kubebuilder:scaffold:scheme 74 | 75 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 76 | Expect(err).NotTo(HaveOccurred()) 77 | Expect(k8sClient).NotTo(BeNil()) 78 | }) 79 | 80 | var _ = AfterSuite(func() { 81 | By("tearing down the test environment") 82 | err := testEnv.Stop() 83 | Expect(err).NotTo(HaveOccurred()) 84 | }) 85 | -------------------------------------------------------------------------------- /controllers/operator/authentication_controller_test.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | //. "github.com/onsi/gomega" 6 | //"testing" 7 | ) 8 | 9 | var _ = Describe("Authentication Controller", func() { 10 | 11 | //Describe("", func() { 12 | // var r *AuthenticationReconciler 13 | // var authCR *operatorv1alpha1.Authentication 14 | // var cb fakeclient.ClientBuilder 15 | // var cl client.WithWatch 16 | // Context("When ", func() { 17 | // BeforeEach(func() { 18 | // authCR = &operatorv1alpha1.Authentication{ 19 | // TypeMeta: metav1.TypeMeta{ 20 | // APIVersion: "operator.ibm.com/v1alpha1", 21 | // Kind: "Authentication", 22 | // }, 23 | // ObjectMeta: metav1.ObjectMeta{ 24 | // Name: "example-authentication", 25 | // Namespace: "data-ns", 26 | // ResourceVersion: trackerAddResourceVersion, 27 | // }, 28 | // } 29 | // scheme := runtime.NewScheme() 30 | // Expect(corev1.AddToScheme(scheme)).To(Succeed()) 31 | // Expect(operatorv1alpha1.AddToScheme(scheme)).To(Succeed()) 32 | // cb = *fakeclient.NewClientBuilder(). 33 | // WithScheme(scheme) 34 | // cl = cb.Build() 35 | // r = &AuthenticationReconciler{ 36 | // Client: cl, 37 | // } 38 | // }) 39 | // It("", func() { 40 | 41 | // }) 42 | // }) 43 | 44 | //}) 45 | }) 46 | -------------------------------------------------------------------------------- /controllers/operator/clusterrole.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2023 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package operator 18 | 19 | import ( 20 | "context" 21 | 22 | operatorv1alpha1 "github.com/IBM/ibm-iam-operator/apis/operator/v1alpha1" 23 | ctrlcommon "github.com/IBM/ibm-iam-operator/controllers/common" 24 | "github.com/opdev/subreconciler" 25 | rbacv1 "k8s.io/api/rbac/v1" 26 | k8sErrors "k8s.io/apimachinery/pkg/api/errors" 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | ctrl "sigs.k8s.io/controller-runtime" 29 | ) 30 | 31 | func (r *AuthenticationReconciler) handleClusterRoles(ctx context.Context, req ctrl.Request) (result *ctrl.Result, err error) { 32 | reqLogger := log.WithValues("subreconciler", "handleClusterRoles") 33 | 34 | canCreateClusterRoles, err := r.hasAPIAccess(ctx, "", rbacv1.SchemeGroupVersion.Group, "clusterroles", []string{"create"}) 35 | if !canCreateClusterRoles { 36 | reqLogger.Info("The Operator's ServiceAccount does not have the necessary accesses to create the ClusterRole; skipping") 37 | return subreconciler.ContinueReconciling() 38 | } else if err != nil { 39 | return subreconciler.RequeueWithError(err) 40 | } 41 | 42 | if !ctrlcommon.ClusterHasOpenShiftUserGroupVersion(&r.DiscoveryClient) { 43 | reqLogger.Info("user.openshift.io/v1 was not found on the cluster; skipping") 44 | return subreconciler.ContinueReconciling() 45 | } 46 | 47 | authCR := &operatorv1alpha1.Authentication{} 48 | if result, err = r.getLatestAuthentication(ctx, req, authCR); subreconciler.ShouldHaltOrRequeue(result, err) { 49 | return 50 | } 51 | 52 | operandClusterRole := &rbacv1.ClusterRole{ 53 | ObjectMeta: metav1.ObjectMeta{ 54 | Name: "ibm-iam-operand-restricted", 55 | Labels: map[string]string{"app.kubernetes.io/instance": "ibm-iam-operator", "app.kubernetes.io/managed-by": "ibm-iam-operator", "app.kubernetes.io/name": "ibm-iam-operator"}, 56 | }, 57 | Rules: []rbacv1.PolicyRule{ 58 | { 59 | APIGroups: []string{"user.openshift.io"}, 60 | Resources: []string{"users", "groups", "identities"}, 61 | Verbs: []string{"get", "list"}, 62 | }, 63 | }, 64 | } 65 | reqLogger = reqLogger.WithValues("ClusterRole.Name", operandClusterRole.Name) 66 | if err := r.Create(ctx, operandClusterRole); k8sErrors.IsAlreadyExists(err) { 67 | reqLogger.Info("ClusterRole already exists; continuing") 68 | return subreconciler.ContinueReconciling() 69 | } else if err != nil { 70 | reqLogger.Info("Encountered an unexpected error while trying to create ClusterRole", "error", err.Error()) 71 | return subreconciler.RequeueWithDelay(defaultLowerWait) 72 | } 73 | reqLogger.Info("ClusterRole created") 74 | return subreconciler.RequeueWithDelay(defaultLowerWait) 75 | } 76 | -------------------------------------------------------------------------------- /controllers/operator/clusterrolebinding.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package operator 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | operatorv1alpha1 "github.com/IBM/ibm-iam-operator/apis/operator/v1alpha1" 24 | ctrlcommon "github.com/IBM/ibm-iam-operator/controllers/common" 25 | "github.com/opdev/subreconciler" 26 | rbacv1 "k8s.io/api/rbac/v1" 27 | k8sErrors "k8s.io/apimachinery/pkg/api/errors" 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | ctrl "sigs.k8s.io/controller-runtime" 30 | logf "sigs.k8s.io/controller-runtime/pkg/log" 31 | ) 32 | 33 | // handleClusterRoleBindings creates a ClusterRoleBinding that binds the ibm-iam-operand-restricted ClusterRole to the 34 | // ibm-iam-operand-restricted ServiceAccount in the services namespace for this Authentication instance. 35 | func (r *AuthenticationReconciler) handleClusterRoleBindings(ctx context.Context, req ctrl.Request) (result *ctrl.Result, err error) { 36 | reqLogger := logf.FromContext(ctx).WithValues("subreconciler", "handleClusterRoleBindings") 37 | reqLogger.Info("Ensure that the ClusterRoleBinding is created") 38 | 39 | canCreateCRB, err := r.hasAPIAccess(ctx, "", rbacv1.SchemeGroupVersion.Group, "clusterrolebindings", []string{"create"}) 40 | if !canCreateCRB { 41 | reqLogger.Info("The Operator's ServiceAccount does not have the necessary accesses to create the ClusterRoleBinding; skipping") 42 | return subreconciler.ContinueReconciling() 43 | } else if err != nil { 44 | return subreconciler.RequeueWithError(err) 45 | } 46 | 47 | if !ctrlcommon.ClusterHasOpenShiftUserGroupVersion(&r.DiscoveryClient) { 48 | reqLogger.Info("user.openshift.io/v1 was not found on the cluster; skipping") 49 | return subreconciler.ContinueReconciling() 50 | } 51 | 52 | authCR := &operatorv1alpha1.Authentication{} 53 | if result, err = r.getLatestAuthentication(ctx, req, authCR); subreconciler.ShouldHaltOrRequeue(result, err) { 54 | return 55 | } 56 | name := fmt.Sprintf("ibm-iam-operand-restricted-%s", authCR.Namespace) 57 | 58 | operandCRB := &rbacv1.ClusterRoleBinding{ 59 | ObjectMeta: metav1.ObjectMeta{ 60 | Name: name, 61 | Labels: map[string]string{ 62 | "app.kubernetes.io/instance": "ibm-iam-operator", 63 | "app.kubernetes.io/managed-by": "ibm-iam-operator", 64 | "app.kubernetes.io/name": "ibm-iam-operator", 65 | }, 66 | }, 67 | RoleRef: rbacv1.RoleRef{ 68 | APIGroup: rbacv1.GroupName, 69 | Kind: "ClusterRole", 70 | Name: "ibm-iam-operand-restricted", 71 | }, 72 | Subjects: []rbacv1.Subject{ 73 | { 74 | Kind: "ServiceAccount", 75 | Name: "ibm-iam-operand-restricted", 76 | Namespace: req.Namespace, 77 | }, 78 | }, 79 | } 80 | 81 | reqLogger = reqLogger.WithValues("ClusterRoleBinding.Name", name) 82 | if err = r.Create(ctx, operandCRB); k8sErrors.IsAlreadyExists(err) { 83 | reqLogger.Info("ClusterRoleBinding already exists, continuing") 84 | return subreconciler.ContinueReconciling() 85 | } else if err != nil { 86 | reqLogger.Error(err, "Failed to create ClusterRoleBinding") 87 | return subreconciler.RequeueWithError(err) 88 | } 89 | reqLogger.Info("Created ClusterRoleBinding successfully") 90 | return subreconciler.RequeueWithDelay(defaultLowerWait) 91 | } 92 | -------------------------------------------------------------------------------- /controllers/operator/constants.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package operator 18 | 19 | const ( 20 | // ClusterConfigName ... ibmcloud-cluster-info 21 | ClusterAddr string = "cluster_address" 22 | ClusterEP string = "cluster_endpoint" 23 | IMCrtAuthRouteName string = "im-certauth-passthrough" 24 | IMCrtAuthRoutePrefix string = "passthrough" 25 | RouteHTTPPort string = "cluster_router_http_port" 26 | RouteHTTPSPort string = "cluster_router_https_port" 27 | RouteHTTPPortValue string = "80" 28 | RouteHTTPSPortValue string = "443" 29 | ClusterName string = "cluster_name" 30 | ClusterNameValue string = "mycluster" 31 | ClusterAPIServerHost string = "cluster_kube_apiserver_host" 32 | ClusterAPIServerPort string = "cluster_kube_apiserver_port" 33 | ClusterSecretName string = "ibmcloud-cluster-ca-cert" 34 | ProxyAddress string = "proxy_address" 35 | ProviderSVC string = "im_idprovider_endpoint" 36 | IDMgmtSVC string = "im_idmgmt_endpoint" 37 | ) 38 | 39 | var ArchList = []string{ 40 | "amd64", 41 | "ppc64le", 42 | "s390x", 43 | } 44 | 45 | const registerClientScript = `#!/bin/sh 46 | HTTP_CODE="" 47 | while true 48 | do 49 | HTTP_CODE=$(curl -k -o /dev/null -I -w "%{http_code}" -X GET -u oauthadmin:$WLP_CLIENT_REGISTRATION_SECRET -H "Content-Type: application/json" https://platform-auth-service:9443/oidc/endpoint/OP/registration/$WLP_CLIENT_ID) 50 | if [ $HTTP_CODE = "404" -o $HTTP_CODE = "200" ] ; then 51 | break; 52 | fi 53 | done 54 | if [ $HTTP_CODE = "404" ] 55 | then 56 | echo "Running new client id registration" 57 | cp /jsons/platform-oidc-registration.json platform-oidc-registration.json 58 | until curl -i -k -X POST -u oauthadmin:$WLP_CLIENT_REGISTRATION_SECRET \ 59 | -H "Content-Type: application/json" \ 60 | --data @platform-oidc-registration.json \ 61 | https://platform-auth-service:9443/oidc/endpoint/OP/registration | grep '201 Created'; do sleep 1; done; 62 | else 63 | echo "Running update client id registration." 64 | cp /jsons/platform-oidc-registration.json platform-oidc-registration.json 65 | until curl -i -k -X PUT -u oauthadmin:$WLP_CLIENT_REGISTRATION_SECRET \ 66 | -H "Content-Type: application/json" \ 67 | --data @platform-oidc-registration.json \ 68 | https://platform-auth-service:9443/oidc/endpoint/OP/registration/$WLP_CLIENT_ID | grep '200 OK'; do sleep 1; done; 69 | fi 70 | ` 71 | 72 | var registrationJson string = `{ 73 | "token_endpoint_auth_method": "client_secret_basic", 74 | "client_id": {{printf "%q" .WLPClientID}}, 75 | "client_secret": {{printf "%q" .WLPClientSecret}}, 76 | "scope": "openid profile email", 77 | "grant_types": [ 78 | "authorization_code", 79 | "client_credentials", 80 | "password", 81 | "implicit", 82 | "refresh_token", 83 | "urn:ietf:params:oauth:grant-type:jwt-bearer" 84 | ], 85 | "response_types": [ 86 | "code", 87 | "token", 88 | "id_token token" 89 | ], 90 | "application_type": "web", 91 | "subject_type": "public", 92 | "post_logout_redirect_uris": ["https://{{.ICPConsoleURL}}/console/logout"], 93 | "preauthorized_scope": "openid profile email general", 94 | "introspect_tokens": true, 95 | "functional_user_groupIds": ["Administrator"], 96 | "trusted_uri_prefixes": ["https://{{.ICPConsoleURL}}"], 97 | "redirect_uris": [{{ range $_, $url := .ICPRegistrationConsoleURIs}}{{printf "%q" $url}}{{", "}}{{end}}"https://127.0.0.1:443/idauth/oidc/endpoint/OP"] 98 | }` 99 | 100 | var scimLdapAttributesMapping string = `{ 101 | "default": { 102 | "user": { 103 | "id": "dn", 104 | "userName": "uid", 105 | "principalName": "uid", 106 | "displayName": "cn", 107 | "givenName": "cn", 108 | "familyName": "sn", 109 | "fullName": "cn", 110 | "externalId": "dn", 111 | "emails": "mail", 112 | "created": "createTimestamp", 113 | "lastModified": "modifyTimestamp", 114 | "phoneNumbers": [{ 115 | "value": "mobile", 116 | "type": "mobile" 117 | }, 118 | { 119 | "value": "telephoneNumber", 120 | "type": "work" 121 | }], 122 | "objectClass": "person", 123 | "groups": "memberOf" 124 | }, 125 | "group": { 126 | "id": "dn", 127 | "name": "cn", 128 | "principalName": "cn", 129 | "displayName": "cn", 130 | "externalId": "dn", 131 | "created": "createTimestamp", 132 | "lastModified": "modifyTimestamp", 133 | "objectClass": "groupOfUniqueNames", 134 | "members": "uniqueMember" 135 | } 136 | } 137 | } 138 | ` 139 | -------------------------------------------------------------------------------- /controllers/operator/deployment_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2025 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package operator 18 | 19 | import ( 20 | . "github.com/onsi/ginkgo/v2" 21 | . "github.com/onsi/gomega" 22 | appsv1 "k8s.io/api/apps/v1" 23 | v1 "k8s.io/api/core/v1" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | ) 26 | 27 | var _ = Describe("Deployment handling", func() { 28 | DescribeTable("hasDataField", 29 | func(b []byte, has bool) { 30 | fields := metav1.ManagedFieldsEntry{ 31 | FieldsV1: &metav1.FieldsV1{ 32 | Raw: b, 33 | }, 34 | } 35 | Expect(hasDataField(fields)).To(Equal(has)) 36 | }, 37 | Entry("has a modified \"data\" field", 38 | []byte(`{"manager": "ibm-iam-operator","operation": "Update", "apiVersion": "v1", "time": "2025-03-11T17:19:25Z", "fieldsType": "FieldsV1", "fieldsV1": {"f:data":{"f:proxy_address":{}}}}`), 39 | true, 40 | ), 41 | Entry("is empty JSON", 42 | []byte(`{}`), 43 | false, 44 | ), 45 | Entry("is broken JSON", 46 | []byte(`{`), 47 | false, 48 | ), 49 | Entry("is empty slice", 50 | []byte(``), 51 | false, 52 | ), 53 | ) 54 | DescribeTable("preserveObservedFields", 55 | func(observed, generated *appsv1.Deployment) { 56 | preserveObservedFields(observed, generated) 57 | for _, observedContainer := range observed.Spec.Template.Spec.Containers { 58 | for _, generatedContainer := range generated.Spec.Template.Spec.Containers { 59 | Expect(generatedContainer).To(Equal(observedContainer)) 60 | } 61 | } 62 | for _, observedContainer := range observed.Spec.Template.Spec.InitContainers { 63 | for _, generatedContainer := range generated.Spec.Template.Spec.InitContainers { 64 | Expect(generatedContainer).To(Equal(observedContainer)) 65 | } 66 | } 67 | }, 68 | Entry("copies containers and initcontainers to generated from observed successfully", 69 | &appsv1.Deployment{ 70 | Spec: appsv1.DeploymentSpec{ 71 | Template: v1.PodTemplateSpec{ 72 | Spec: v1.PodSpec{ 73 | Containers: []v1.Container{ 74 | { 75 | Name: "platform-auth-service", 76 | LivenessProbe: &v1.Probe{ 77 | FailureThreshold: 15, 78 | PeriodSeconds: 10, 79 | SuccessThreshold: 1, 80 | }, 81 | ReadinessProbe: &v1.Probe{ 82 | SuccessThreshold: 1, 83 | }, 84 | TerminationMessagePath: "/tmp/test", 85 | TerminationMessagePolicy: v1.TerminationMessageReadFile, 86 | }, 87 | }, 88 | InitContainers: []v1.Container{ 89 | { 90 | Name: "init-db", 91 | TerminationMessagePath: "/tmp/test", 92 | TerminationMessagePolicy: v1.TerminationMessageReadFile, 93 | }, 94 | }, 95 | }, 96 | }, 97 | }, 98 | }, 99 | &appsv1.Deployment{ 100 | Spec: appsv1.DeploymentSpec{ 101 | Template: v1.PodTemplateSpec{ 102 | Spec: v1.PodSpec{ 103 | Containers: []v1.Container{ 104 | { 105 | Name: "platform-auth-service", 106 | }, 107 | }, 108 | InitContainers: []v1.Container{ 109 | { 110 | Name: "init-db", 111 | }, 112 | }, 113 | }, 114 | }, 115 | }, 116 | }, 117 | ), 118 | ) 119 | }) 120 | -------------------------------------------------------------------------------- /controllers/operator/operandbindinfo.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2025 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package operator 18 | 19 | import ( 20 | "context" 21 | "reflect" 22 | 23 | operatorv1alpha1 "github.com/IBM/ibm-iam-operator/apis/operator/v1alpha1" 24 | ctrlcommon "github.com/IBM/ibm-iam-operator/controllers/common" 25 | "github.com/opdev/subreconciler" 26 | k8sErrors "k8s.io/apimachinery/pkg/api/errors" 27 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | "k8s.io/apimachinery/pkg/runtime" 29 | "k8s.io/apimachinery/pkg/types" 30 | ctrl "sigs.k8s.io/controller-runtime" 31 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 32 | logf "sigs.k8s.io/controller-runtime/pkg/log" 33 | ) 34 | 35 | const bindInfoName = "ibm-iam-bindinfo" 36 | 37 | func (r *AuthenticationReconciler) handleOperandBindInfo(ctx context.Context, req ctrl.Request) (result *ctrl.Result, err error) { 38 | reqLogger := logf.FromContext(ctx).WithValues( 39 | "subreconciler", "handleOperandBindInfo", 40 | "OperandBindInfo.Name", bindInfoName, 41 | "OperandBindInfo.Namespace", req.Namespace) 42 | 43 | if !ctrlcommon.ClusterHasOperandBindInfoAPIResource(&r.DiscoveryClient) { 44 | reqLogger.Info("The OperandBindInfo API resource is not supported by this cluster") 45 | return subreconciler.ContinueReconciling() 46 | } 47 | 48 | reqLogger.Info("Ensure that OperandBindInfo is present") 49 | authCR := &operatorv1alpha1.Authentication{} 50 | if result, err = r.getLatestAuthentication(ctx, req, authCR); subreconciler.ShouldHaltOrRequeue(result, err) { 51 | return 52 | } 53 | 54 | generated := &operatorv1alpha1.OperandBindInfo{} 55 | if err = generateOperandBindInfo(authCR, r.Client.Scheme(), generated); err != nil { 56 | reqLogger.Error(err, "Failed to generate OperandBindInfo") 57 | return subreconciler.RequeueWithError(err) 58 | } 59 | observed := &operatorv1alpha1.OperandBindInfo{} 60 | 61 | err = r.Get(ctx, types.NamespacedName{Name: bindInfoName, Namespace: req.Namespace}, observed) 62 | if k8sErrors.IsNotFound(err) { 63 | if err = r.Create(ctx, generated); err != nil { 64 | reqLogger.Error(err, "Encountered an unexpected error while creating OperandBindInfo") 65 | return subreconciler.RequeueWithError(err) 66 | } 67 | reqLogger.Info("OperandBindInfo created; requeueing") 68 | return subreconciler.RequeueWithDelay(defaultLowerWait) 69 | } else if err != nil { 70 | return subreconciler.RequeueWithError(err) 71 | } 72 | 73 | updated := false 74 | if !reflect.DeepEqual(observed.Spec, generated.Spec) { 75 | reqLogger.Info("OperandBindInfo specs differ; updating") 76 | observed.Spec = generated.Spec 77 | updated = true 78 | } 79 | 80 | if !ctrlcommon.IsOwnerOf(r.Client.Scheme(), authCR, observed) { 81 | if err = controllerutil.SetOwnerReference(authCR, observed, r.Client.Scheme()); err != nil { 82 | reqLogger.Error(err, "Failed to set owner reference on OperandBindInfo") 83 | return subreconciler.RequeueWithError(err) 84 | } 85 | updated = true 86 | } 87 | 88 | if updated { 89 | if err = r.Update(ctx, observed); err != nil { 90 | reqLogger.Error(err, "Encountered an unexpected error while updating OperandBindInfo") 91 | return subreconciler.RequeueWithError(err) 92 | } 93 | reqLogger.Info("Updated the OperandBindInfo; requeueing") 94 | return subreconciler.RequeueWithDelay(defaultLowerWait) 95 | } 96 | 97 | reqLogger.Info("No changes to OperandBindInfo; continue") 98 | return subreconciler.ContinueReconciling() 99 | } 100 | 101 | func generateOperandBindInfo(authCR *operatorv1alpha1.Authentication, scheme *runtime.Scheme, generated *operatorv1alpha1.OperandBindInfo) (err error) { 102 | *generated = operatorv1alpha1.OperandBindInfo{ 103 | ObjectMeta: v1.ObjectMeta{ 104 | Name: bindInfoName, 105 | Namespace: authCR.Namespace, 106 | }, 107 | TypeMeta: v1.TypeMeta{ 108 | APIVersion: "operator.ibm.com/v1alpha1", 109 | Kind: "OperandBindInfo", 110 | }, 111 | Spec: operatorv1alpha1.OperandBindInfoSpec{ 112 | Operand: "ibm-im-operator", 113 | Registry: "common-service", 114 | Description: "Binding information that should be accessible to iam adopters", 115 | Bindings: map[string]operatorv1alpha1.Bindable{ 116 | "public-oidc-creds": { 117 | Secret: "platform-oidc-credentials", 118 | }, 119 | "public-auth-creds": { 120 | Secret: "platform-auth-idp-credentials", 121 | }, 122 | "public-scim-creds": { 123 | Secret: "platform-auth-scim-credentials", 124 | }, 125 | "public-auth-cert": { 126 | Secret: "platform-auth-secret", 127 | }, 128 | "public-cam-secret": { 129 | Secret: "oauth-client-secret", 130 | }, 131 | "public-cam-map": { 132 | Configmap: "oauth-client-map", 133 | }, 134 | "public-auth-config": { 135 | Configmap: "platform-auth-idp", 136 | }, 137 | "public-ibmcloud-config": { 138 | Configmap: "ibmcloud-cluster-info", 139 | }, 140 | "public-ibmcloudca-secret": { 141 | Secret: "ibmcloud-cluster-ca-cert", 142 | }, 143 | }, 144 | }, 145 | } 146 | err = controllerutil.SetOwnerReference(authCR, generated, scheme) 147 | return 148 | } 149 | -------------------------------------------------------------------------------- /controllers/operator/role.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2023 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package operator 18 | 19 | import ( 20 | "context" 21 | 22 | operatorv1alpha1 "github.com/IBM/ibm-iam-operator/apis/operator/v1alpha1" 23 | rbacv1 "k8s.io/api/rbac/v1" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | ) 26 | 27 | func (r *AuthenticationReconciler) createRole(instance *operatorv1alpha1.Authentication) { 28 | 29 | reqLogger := log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) 30 | // Define a new Role 31 | operandRole := r.iamOperandRole(instance) 32 | reqLogger.Info("Creating ibm-iam-operand-restricted role") 33 | err := r.Client.Create(context.TODO(), operandRole) 34 | if err != nil { 35 | reqLogger.Info("Failed to create ibm-iam-operand-restricted role or its already present") 36 | } 37 | // Role created successfully - return and requeue 38 | 39 | } 40 | func (r *AuthenticationReconciler) iamOperandRole(instance *operatorv1alpha1.Authentication) *rbacv1.Role { 41 | 42 | // reqLogger := log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) 43 | operandRole := &rbacv1.Role{ 44 | ObjectMeta: metav1.ObjectMeta{ 45 | Name: "ibm-iam-operand-restricted", 46 | Labels: map[string]string{"app.kubernetes.io/instance": "ibm-iam-operator", "app.kubernetes.io/managed-by": "ibm-iam-operator", "app.kubernetes.io/name": "ibm-iam-operator"}, 47 | Namespace: instance.Namespace, 48 | }, 49 | Rules: []rbacv1.PolicyRule{ 50 | { 51 | APIGroups: []string{"oidc.security.ibm.com"}, 52 | Resources: []string{"clients", "clients/finalizers", "clients/status"}, 53 | Verbs: []string{"create", "delete", "watch", "get", "list", "patch", "update"}, 54 | }, 55 | { 56 | APIGroups: []string{""}, 57 | Resources: []string{"secrets", "services", "endpoints"}, 58 | Verbs: []string{"create", "delete", "watch", "get", "list", "patch", "update"}, 59 | }, 60 | }, 61 | } 62 | return operandRole 63 | 64 | } 65 | -------------------------------------------------------------------------------- /controllers/operator/rolebinding.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package operator 18 | 19 | import ( 20 | "context" 21 | 22 | operatorv1alpha1 "github.com/IBM/ibm-iam-operator/apis/operator/v1alpha1" 23 | rbacv1 "k8s.io/api/rbac/v1" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | ) 26 | 27 | func (r *AuthenticationReconciler) createRoleBinding(instance *operatorv1alpha1.Authentication) { 28 | 29 | reqLogger := log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) 30 | // Define a new RoleBinding 31 | operandRB := r.iamOperandRB(instance) 32 | reqLogger.Info("Creating ibm-iam-operand-restricted RoleBinding") 33 | err := r.Client.Create(context.TODO(), operandRB) 34 | if err != nil { 35 | reqLogger.Info("Failed to create ibm-iam-operand-restricted RoleBinding or its already present") 36 | } 37 | 38 | } 39 | func (r *AuthenticationReconciler) iamOperandRB(instance *operatorv1alpha1.Authentication) *rbacv1.RoleBinding { 40 | 41 | // reqLogger := log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) 42 | operandRB := &rbacv1.RoleBinding{ 43 | ObjectMeta: metav1.ObjectMeta{ 44 | Name: "ibm-iam-operand-restricted", 45 | Namespace: instance.Namespace, 46 | Labels: map[string]string{"app.kubernetes.io/instance": "ibm-iam-operator", "app.kubernetes.io/managed-by": "ibm-iam-operator", "app.kubernetes.io/name": "ibm-iam-operator"}, 47 | }, 48 | RoleRef: rbacv1.RoleRef{ 49 | APIGroup: rbacv1.GroupName, 50 | Kind: "Role", 51 | Name: "ibm-iam-operand-restricted", 52 | }, 53 | Subjects: []rbacv1.Subject{ 54 | { 55 | Kind: "ServiceAccount", 56 | Name: "ibm-iam-operand-restricted", 57 | Namespace: instance.Namespace, 58 | }, 59 | }, 60 | } 61 | return operandRB 62 | 63 | } 64 | -------------------------------------------------------------------------------- /controllers/operator/sacc.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2023 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package operator 18 | 19 | import ( 20 | "context" 21 | 22 | operatorv1alpha1 "github.com/IBM/ibm-iam-operator/apis/operator/v1alpha1" 23 | 24 | corev1 "k8s.io/api/core/v1" 25 | "k8s.io/apimachinery/pkg/api/errors" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | "k8s.io/apimachinery/pkg/runtime" 28 | "k8s.io/apimachinery/pkg/types" 29 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 30 | ) 31 | 32 | func (r *AuthenticationReconciler) createSA(instance *operatorv1alpha1.Authentication, currentSA *corev1.ServiceAccount, needToRequeue *bool) (err error) { 33 | 34 | reqLogger := log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) 35 | operandSAName := "ibm-iam-operand-restricted" 36 | 37 | err = r.Client.Get(context.TODO(), types.NamespacedName{Name: operandSAName, Namespace: instance.Namespace}, currentSA) 38 | if err != nil && errors.IsNotFound(err) { 39 | reqLogger.Info("Did not find ServiceAccount", "name", operandSAName, "namespace", instance.Namespace) 40 | // Define a new operand ServiceAccount 41 | operandSA := generateSAObject(instance, r.Scheme, operandSAName) 42 | reqLogger.Info("Creating a ibm-iam-operand-restricted serviceaccount") 43 | err = r.Client.Create(context.TODO(), operandSA) 44 | if err != nil { 45 | reqLogger.Error(err, "Failed to create ibm-iam-operand-restricted serviceaccount") 46 | return 47 | } 48 | // serviceaccount created successfully - return and requeue 49 | *needToRequeue = true 50 | } else if err != nil { 51 | reqLogger.Error(err, "Failed to get serviceaccount") 52 | return 53 | } 54 | 55 | return 56 | } 57 | 58 | func (r *AuthenticationReconciler) handleServiceAccount(instance *operatorv1alpha1.Authentication, needToRequeue *bool) { 59 | 60 | reqLogger := log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) 61 | // step 1. Get console url to form redirecturi 62 | consoleURL := r.getConsoleURL(instance, needToRequeue) 63 | var redirectURI string 64 | if consoleURL == "" { 65 | reqLogger.Info("Problem retriving consoleURL") 66 | } else { 67 | redirectURI = "https://" + consoleURL + "/auth/liberty/callback" 68 | } 69 | // Get exsting annotations from SA 70 | sAccName := "ibm-iam-operand-restricted" 71 | serviceAccount := &corev1.ServiceAccount{} 72 | err := r.Client.Get(context.TODO(), types.NamespacedName{Name: sAccName, Namespace: instance.Namespace}, serviceAccount) 73 | if err != nil { 74 | reqLogger.Error(err, "failed to GET ServiceAccount ibm-iam-operand-restricted") 75 | } else { 76 | if serviceAccount.ObjectMeta.Annotations != nil { 77 | serviceAccount.ObjectMeta.Annotations["serviceaccounts.openshift.io/oauth-redirecturi.first"] = redirectURI 78 | } else { 79 | serviceAccount.ObjectMeta.Annotations = make(map[string]string) 80 | serviceAccount.ObjectMeta.Annotations["serviceaccounts.openshift.io/oauth-redirecturi.first"] = redirectURI 81 | } 82 | // update the SAcc with this annotation 83 | errUpdate := r.Client.Update(context.TODO(), serviceAccount) 84 | if errUpdate != nil { 85 | // error updating annotation 86 | reqLogger.Error(errUpdate, "error updating annotation in ServiceAccount") 87 | } else { 88 | // annotation got updated properly 89 | reqLogger.Info("ibm-iam-operand-restricted SA is updated with annotation successfully") 90 | } 91 | } 92 | return 93 | } 94 | 95 | func generateSAObject(instance *operatorv1alpha1.Authentication, scheme *runtime.Scheme, operndSAName string) *corev1.ServiceAccount { 96 | reqLogger := log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) 97 | metaLabels := map[string]string{ 98 | "app.kubernetes.io/instance": "ibm-iam-operator", 99 | "app.kubernetes.io/managed-by": "ibm-iam-operator", 100 | "app.kubernetes.io/name": "ibm-iam-operator", 101 | } 102 | 103 | operandSA := &corev1.ServiceAccount{ 104 | ObjectMeta: metav1.ObjectMeta{ 105 | Name: operndSAName, 106 | Labels: metaLabels, 107 | Namespace: instance.Namespace, 108 | }, 109 | } 110 | 111 | // Set Authentication instance as the owner and controller of the operand serviceaccount 112 | err := controllerutil.SetControllerReference(instance, operandSA, scheme) 113 | if err != nil { 114 | reqLogger.Error(err, "Failed to set owner for serviceaccount") 115 | return nil 116 | } 117 | return operandSA 118 | } 119 | 120 | // getConsoleURL retrives the cp-console host 121 | func (r *AuthenticationReconciler) getConsoleURL(instance *operatorv1alpha1.Authentication, needToRequeue *bool) (icpConsoleURL string) { 122 | 123 | reqLogger := log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) 124 | proxyConfigMapName := "ibmcloud-cluster-info" 125 | proxyConfigMap := &corev1.ConfigMap{} 126 | err := r.Client.Get(context.TODO(), types.NamespacedName{Name: proxyConfigMapName, Namespace: instance.Namespace}, proxyConfigMap) 127 | if err != nil { 128 | if errors.IsNotFound(err) { 129 | reqLogger.Error(err, "The configmap ", proxyConfigMapName, " is not created yet") 130 | return 131 | } 132 | reqLogger.Error(err, "Failed to get ConfigMap", proxyConfigMapName) 133 | *needToRequeue = true 134 | return 135 | } 136 | var ok bool 137 | icpConsoleURL, ok = proxyConfigMap.Data["cluster_address"] 138 | 139 | if !ok { 140 | reqLogger.Error(nil, "The configmap", proxyConfigMapName, "doesn't contain cluster_address address") 141 | *needToRequeue = true 142 | return 143 | } 144 | return 145 | } 146 | -------------------------------------------------------------------------------- /controllers/operator/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package operator 18 | 19 | import ( 20 | "path/filepath" 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo/v2" 24 | . "github.com/onsi/gomega" 25 | 26 | "k8s.io/client-go/kubernetes/scheme" 27 | "k8s.io/client-go/rest" 28 | "sigs.k8s.io/controller-runtime/pkg/client" 29 | "sigs.k8s.io/controller-runtime/pkg/envtest" 30 | logf "sigs.k8s.io/controller-runtime/pkg/log" 31 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 32 | 33 | operatorv1alpha1 "github.com/IBM/ibm-iam-operator/apis/operator/v1alpha1" 34 | //+kubebuilder:scaffold:imports 35 | ) 36 | 37 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 38 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 39 | 40 | var cfg *rest.Config 41 | var k8sClient client.Client 42 | var testEnv *envtest.Environment 43 | 44 | func TestAPIs(t *testing.T) { 45 | RegisterFailHandler(Fail) 46 | 47 | RunSpecs(t, "Controller Suite") 48 | } 49 | 50 | var _ = BeforeSuite(func() { 51 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 52 | 53 | By("bootstrapping test environment") 54 | testEnv = &envtest.Environment{ 55 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, 56 | ErrorIfCRDPathMissing: true, 57 | } 58 | 59 | var err error 60 | // cfg is defined in this file globally. 61 | cfg, err = testEnv.Start() 62 | Expect(err).NotTo(HaveOccurred()) 63 | Expect(cfg).NotTo(BeNil()) 64 | 65 | err = operatorv1alpha1.AddToScheme(scheme.Scheme) 66 | Expect(err).NotTo(HaveOccurred()) 67 | 68 | //+kubebuilder:scaffold:scheme 69 | 70 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 71 | Expect(err).NotTo(HaveOccurred()) 72 | Expect(k8sClient).NotTo(BeNil()) 73 | 74 | }) 75 | 76 | var _ = AfterSuite(func() { 77 | By("tearing down the test environment") 78 | err := testEnv.Stop() 79 | Expect(err).NotTo(HaveOccurred()) 80 | }) 81 | -------------------------------------------------------------------------------- /controllers/operator/testdata/crds/routes/route_crd.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apiextensions.k8s.io/v1 16 | kind: CustomResourceDefinition 17 | metadata: 18 | # name must match the spec fields below, and be in the form: . 19 | name: routes.route.openshift.io 20 | spec: 21 | # group name to use for REST API: /apis// 22 | group: route.openshift.io 23 | # list of versions supported by this CustomResourceDefinition 24 | versions: 25 | - name: v1 26 | # Each version can be enabled/disabled by Served flag. 27 | served: true 28 | # One and only one version must be marked as the storage version. 29 | storage: true 30 | schema: 31 | openAPIV3Schema: 32 | type: object 33 | x-kubernetes-preserve-unknown-fields: true 34 | additionalPrinterColumns: 35 | - name: Host 36 | type: string 37 | jsonPath: .status.ingress[0].host 38 | - name: Admitted 39 | type: string 40 | jsonPath: .status.ingress[0].conditions[?(@.type=="Admitted")].status 41 | - name: Service 42 | type: string 43 | jsonPath: .spec.to.name 44 | - name: TLS 45 | type: string 46 | jsonPath: .spec.tls.type 47 | subresources: 48 | # enable spec/status 49 | status: {} 50 | # either Namespaced or Cluster 51 | scope: Namespaced 52 | names: 53 | # plural name to be used in the URL: /apis/// 54 | plural: routes 55 | # singular name to be used as an alias on the CLI and for display 56 | singular: route 57 | # kind is normally the CamelCased singular type. Your resource manifests use this. 58 | kind: Route 59 | -------------------------------------------------------------------------------- /controllers/operator/testdata/crds/zen/zenextension_crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: zenextensions.zen.cpd.ibm.com 5 | spec: 6 | conversion: 7 | strategy: None 8 | group: zen.cpd.ibm.com 9 | names: 10 | kind: ZenExtension 11 | listKind: ZenExtensionList 12 | plural: zenextensions 13 | singular: zenextension 14 | scope: Namespaced 15 | versions: 16 | - additionalPrinterColumns: 17 | - description: The status of the zenextension 18 | jsonPath: .status.zenExtensionStatus 19 | name: Status 20 | type: string 21 | - description: The age of the zenextension 22 | jsonPath: .metadata.creationTimestamp 23 | name: Age 24 | type: date 25 | name: v1 26 | schema: 27 | openAPIV3Schema: 28 | description: ZenExtension is the Schema for the zenextensions API 29 | properties: 30 | apiVersion: 31 | 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' 32 | type: string 33 | kind: 34 | 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' 35 | type: string 36 | metadata: 37 | type: object 38 | spec: 39 | description: Spec defines the desired state of ZenExtension 40 | type: object 41 | x-kubernetes-preserve-unknown-fields: true 42 | status: 43 | description: Status defines the observed state of ZenExtension 44 | type: object 45 | x-kubernetes-preserve-unknown-fields: true 46 | type: object 47 | served: true 48 | storage: true 49 | subresources: 50 | status: {} 51 | -------------------------------------------------------------------------------- /database/connectors/dbconn.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package connectors 17 | 18 | import ( 19 | "context" 20 | "fmt" 21 | 22 | "github.com/jackc/pgx/v5" 23 | "github.com/jackc/pgx/v5/pgconn" 24 | "go.mongodb.org/mongo-driver/mongo" 25 | mongoOptions "go.mongodb.org/mongo-driver/mongo/options" 26 | logf "sigs.k8s.io/controller-runtime/pkg/log" 27 | ) 28 | 29 | type DBConn interface { 30 | Connect(context.Context) error 31 | Configure(...DBOption) error 32 | RunDDL(context.Context, string) error 33 | HasSchemas(context.Context) (bool, error) 34 | Disconnect(context.Context) error 35 | LogChanges(context.Context, string, ...any) (pgconn.CommandTag, error) 36 | } 37 | 38 | type PostgresDB struct { 39 | *DBOptions 40 | Conn *pgx.Conn 41 | } 42 | 43 | // LogChanges 44 | func (p *PostgresDB) LogChanges(ctx context.Context, query string, args ...any) (tag pgconn.CommandTag, err error) { 45 | if p.Conn.IsClosed() { 46 | p.Connect(ctx) 47 | defer p.Disconnect(ctx) 48 | } 49 | 50 | return p.Conn.Exec(ctx, query, args...) 51 | } 52 | 53 | // LogChanges is a stub so that MongoDB can satisfy DBConn 54 | func (m *MongoDB) LogChanges(ctx context.Context, query string, args ...any) (tag pgconn.CommandTag, err error) { 55 | return pgconn.CommandTag{}, fmt.Errorf("no changelog defined for MongoDB") 56 | } 57 | 58 | func NewPostgresDB(opts ...DBOption) (*PostgresDB, error) { 59 | p := &PostgresDB{ 60 | DBOptions: &DBOptions{}, 61 | } 62 | if err := p.Configure(opts...); err != nil { 63 | return nil, err 64 | } 65 | return p, nil 66 | } 67 | 68 | func (p *PostgresDB) Connect(ctx context.Context) (err error) { 69 | dsn := fmt.Sprintf("host=%s user=%s dbname=%s port=%s sslmode=require", p.Host, p.User, p.Name, p.Port) 70 | var connConfig *pgx.ConnConfig 71 | if connConfig, err = pgx.ParseConfig(dsn); err != nil { 72 | return err 73 | } 74 | connConfig.TLSConfig = p.TLSConfig 75 | if p.Conn, err = pgx.ConnectConfig(ctx, connConfig); err != nil { 76 | return err 77 | } 78 | 79 | return 80 | } 81 | 82 | func (p *PostgresDB) Disconnect(ctx context.Context) error { 83 | return p.Conn.Close(ctx) 84 | } 85 | 86 | func (p *PostgresDB) RunDDL(ctx context.Context, ddl string) (err error) { 87 | _, err = p.Conn.Exec(ctx, ddl) 88 | return 89 | } 90 | 91 | func (p *PostgresDB) HasSchemas(ctx context.Context) (bool, error) { 92 | reqLogger := logf.FromContext(ctx) 93 | var err error 94 | var rows pgx.Rows 95 | if rows, err = p.Conn.Query(ctx, "SELECT schema_name FROM information_schema.schemata;"); err != nil { 96 | reqLogger.Error(err, "Failed to retrieve schema names") 97 | return false, err 98 | } 99 | defer rows.Close() 100 | 101 | foundSchemas := map[string]bool{} 102 | 103 | for _, schemaName := range p.Schemas { 104 | foundSchemas[schemaName] = false 105 | } 106 | 107 | reqLogger.Info("Schemas to find", "schemas", p.Schemas) 108 | for rows.Next() { 109 | var s string 110 | if err = rows.Scan(&s); err != nil { 111 | return false, err 112 | } 113 | if _, ok := foundSchemas[s]; ok { 114 | foundSchemas[s] = true 115 | } 116 | } 117 | 118 | if rows.Err() != nil { 119 | return false, err 120 | } 121 | 122 | reqLogger.Info("Found schemas", "schemas", foundSchemas) 123 | for _, present := range foundSchemas { 124 | if !present { 125 | return false, nil 126 | } 127 | } 128 | 129 | return true, nil 130 | } 131 | func (p *PostgresDB) HasMetadataSchema(ctx context.Context) (has bool, err error) { 132 | query := "SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = 'metadata'" 133 | var table string 134 | err = p.Conn.QueryRow(ctx, query).Scan(&table) 135 | has = table != "" 136 | return 137 | } 138 | 139 | func (p *PostgresDB) GetInstalledSchemaVersion(ctx context.Context) (version string, err error) { 140 | query := "SELECT schema_version FROM metadata.changelog ORDER BY install_time DESC LIMIT 1;" 141 | err = p.Conn.QueryRow(ctx, query).Scan(&version) 142 | return 143 | } 144 | 145 | var _ DBConn = &PostgresDB{} 146 | 147 | type MongoDB struct { 148 | *DBOptions 149 | Client *mongo.Client 150 | } 151 | 152 | func NewMongoDB(opts ...DBOption) (m *MongoDB, err error) { 153 | m = &MongoDB{ 154 | DBOptions: &DBOptions{}, 155 | } 156 | if err := m.Configure(opts...); err != nil { 157 | return nil, err 158 | } 159 | return m, nil 160 | } 161 | 162 | func (m *MongoDB) Connect(ctx context.Context) (err error) { 163 | uri := fmt.Sprintf("mongodb://%s:%s@%s:%s/?ssl=true&replicaSet=rs0&readPreference=secondaryPreferred&authSource=%s", 164 | m.User, 165 | m.Password, 166 | m.Host, 167 | m.Port, 168 | "admin") 169 | mongoClientOpts := mongoOptions.Client().ApplyURI(uri).SetTLSConfig(m.TLSConfig) 170 | if m.Client, err = mongo.Connect(ctx, mongoClientOpts); err != nil { 171 | return err 172 | } 173 | return 174 | } 175 | 176 | func (m *MongoDB) Disconnect(ctx context.Context) error { 177 | return m.Client.Disconnect(ctx) 178 | } 179 | 180 | func (m *MongoDB) RunDDL(ctx context.Context, ddl string) error { 181 | return fmt.Errorf("does not support executing DDL") 182 | } 183 | 184 | func (m *MongoDB) HasSchemas(ctx context.Context) (bool, error) { 185 | return false, nil 186 | } 187 | 188 | var _ DBConn = &MongoDB{} 189 | -------------------------------------------------------------------------------- /database/connectors/options.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package connectors 17 | 18 | import ( 19 | "crypto/tls" 20 | "crypto/x509" 21 | "fmt" 22 | ) 23 | 24 | type DBOption func(*DBOptions) error 25 | 26 | // DBOptions repesent a series of details about a given database instance 27 | type DBOptions struct { 28 | Name string // the name of the database used in a DSN 29 | ID string // the identifier used for logs, recording migration activity, etc. 30 | Port string // the port used to connect to 31 | User string // the user to authenticate as 32 | Password string // the password to authenticate with 33 | Host string // the database hostname/URL 34 | TLSConfig *tls.Config // the certificates used to authenticate with 35 | Schemas []string // a list of schema names 36 | } 37 | 38 | // GetMigrationKey returns a key name used for writing back successful migration state to some other database 39 | func (o *DBOptions) GetMigrationKey() string { 40 | return fmt.Sprintf("migrated_to_%s", o.ID) 41 | } 42 | 43 | func (o *DBOptions) Configure(opts ...DBOption) (err error) { 44 | for _, option := range opts { 45 | if err = option(o); err != nil { 46 | return 47 | } 48 | } 49 | return 50 | } 51 | 52 | func TLSConfig(caCert, clientCert, clientKey []byte) DBOption { 53 | return func(c *DBOptions) (err error) { 54 | if c == nil { 55 | return 56 | } 57 | caCertPool := x509.NewCertPool() 58 | if ok := caCertPool.AppendCertsFromPEM(caCert); !ok { 59 | return fmt.Errorf("failed to add CA certificate to cert pool") 60 | } 61 | var clientCertificate tls.Certificate 62 | if clientCertificate, err = tls.X509KeyPair(clientCert, clientKey); err != nil { 63 | return err 64 | } 65 | tlsConfig := &tls.Config{ 66 | RootCAs: caCertPool, 67 | Certificates: []tls.Certificate{clientCertificate}, 68 | InsecureSkipVerify: false, 69 | ServerName: c.Host, 70 | } 71 | c.TLSConfig = tlsConfig 72 | return 73 | } 74 | } 75 | 76 | func Name(name string) DBOption { 77 | return func(c *DBOptions) (err error) { 78 | if c != nil { 79 | c.Name = name 80 | } 81 | return 82 | } 83 | } 84 | 85 | func ID(id string) DBOption { 86 | return func(c *DBOptions) (err error) { 87 | if c != nil { 88 | c.ID = id 89 | } 90 | return 91 | } 92 | } 93 | 94 | func Port(port string) DBOption { 95 | return func(c *DBOptions) (err error) { 96 | if c != nil { 97 | c.Port = port 98 | } 99 | return 100 | } 101 | } 102 | 103 | func User(user string) DBOption { 104 | return func(c *DBOptions) (err error) { 105 | if c != nil { 106 | c.User = user 107 | } 108 | return 109 | } 110 | } 111 | 112 | func Password(password string) DBOption { 113 | return func(c *DBOptions) (err error) { 114 | if c != nil { 115 | c.Password = password 116 | } 117 | return 118 | } 119 | } 120 | 121 | func Host(host string) DBOption { 122 | return func(c *DBOptions) (err error) { 123 | if c != nil { 124 | c.Host = host 125 | } 126 | return 127 | } 128 | } 129 | 130 | func Schemas(schemas ...string) DBOption { 131 | return func(c *DBOptions) (err error) { 132 | if c == nil { 133 | return 134 | } 135 | if len(c.Schemas) == 0 { 136 | c.Schemas = []string{} 137 | } 138 | c.Schemas = append(c.Schemas, schemas...) 139 | return 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /database/migrate.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package database 17 | 18 | import ( 19 | "container/heap" 20 | "context" 21 | "errors" 22 | "fmt" 23 | "time" 24 | 25 | dbconn "github.com/IBM/ibm-iam-operator/database/connectors" 26 | "github.com/IBM/ibm-iam-operator/database/migration" 27 | v1schema "github.com/IBM/ibm-iam-operator/database/schema/v1" 28 | "github.com/IBM/ibm-iam-operator/version" 29 | "github.com/jackc/pgx/v5/pgtype" 30 | logf "sigs.k8s.io/controller-runtime/pkg/log" 31 | ) 32 | 33 | // PlanMigrations produces a priority queue of Migrations to perform based upon the database connections that have been 34 | // provided. 35 | func PlanMigrations(ctx context.Context, to, from dbconn.DBConn) (mq *migration.MigrationQueue, err error) { 36 | reqLogger := logf.FromContext(ctx) 37 | var postgres *dbconn.PostgresDB 38 | var mongo *dbconn.MongoDB 39 | if to == nil { 40 | return nil, fmt.Errorf("to should be an instance of Postgres") 41 | } 42 | postgres, ok := to.(*dbconn.PostgresDB) 43 | if !ok { 44 | return nil, fmt.Errorf("to should be an instance of Postgres") 45 | } 46 | 47 | // For the time being, the assumption is that, if data is being copied from one DB to another, the source is 48 | // MongoDB; if that should change, this needs to change. 49 | if from != nil { 50 | mongo, ok = from.(*dbconn.MongoDB) 51 | if !ok { 52 | return nil, fmt.Errorf("from should be an instance of MongoDB") 53 | } 54 | } 55 | mq = &migration.MigrationQueue{} 56 | *mq = make(migration.MigrationQueue, 0) 57 | 58 | reqLogger.Info("Retrieving change logs") 59 | // CreateMetadataSchema needs to be checked for and run a little differently from the others because, without 60 | // it, all attempts to log future schema changes will fail due to the schema/table for that not existing yet. 61 | changelogs, err := v1schema.GetChangelogs(ctx, postgres) 62 | if errors.Is(err, v1schema.ErrTableDoesNotExist) { 63 | err = nil 64 | reqLogger.Info("Table metadata.changelog not present; adding migration", "migrationName", v1schema.CreateMetadataSchema.Name) 65 | *mq = append(*mq, migration.FromMigration(v1schema.CreateMetadataSchema). 66 | To(to). 67 | Build()) 68 | } else if err != nil { 69 | return nil, fmt.Errorf("failed to retrieve changelogs: %w", err) 70 | } 71 | 72 | if _, ok := changelogs[v1schema.InitOperandSchemas.ID]; !ok { 73 | reqLogger.Info("Adding migration", "migrationName", v1schema.InitOperandSchemas.Name) 74 | *mq = append(*mq, migration.FromMigration(v1schema.InitOperandSchemas). 75 | To(to). 76 | Build()) 77 | } 78 | 79 | if _, ok := changelogs[v1schema.IncreaseOIDCUsernameSize.ID]; !ok { 80 | reqLogger.Info("Adding migration", "migrationName", v1schema.IncreaseOIDCUsernameSize.Name) 81 | *mq = append(*mq, migration.FromMigration(v1schema.IncreaseOIDCUsernameSize). 82 | To(to). 83 | Build()) 84 | } 85 | 86 | if _, ok := changelogs[v1schema.IncreaseTokenstringSize.ID]; !ok { 87 | reqLogger.Info("Adding migration", "migrationName", v1schema.IncreaseTokenstringSize.Name) 88 | *mq = append(*mq, migration.FromMigration(v1schema.IncreaseTokenstringSize). 89 | To(to). 90 | Build()) 91 | } 92 | 93 | if _, ok := changelogs[v1schema.AlterUsersAttributesUniqueAndCascadeDeleteConstraint.ID]; !ok { 94 | reqLogger.Info("Adding migration", "migrationName", v1schema.AlterUsersAttributesUniqueAndCascadeDeleteConstraint.Name) 95 | *mq = append(*mq, migration.FromMigration(v1schema.AlterUsersAttributesUniqueAndCascadeDeleteConstraint). 96 | To(to). 97 | Build()) 98 | } 99 | 100 | if _, ok := changelogs[v1schema.MongoToEDBv1.ID]; !ok && mongo != nil { 101 | reqLogger.Info("Adding migration", "migrationName", v1schema.MongoToEDBv1.Name) 102 | *mq = append(*mq, migration.FromMigration(v1schema.MongoToEDBv1). 103 | To(to). 104 | From(from). 105 | Build()) 106 | } 107 | mq.UpdatePrioritiesByDependencyCount() 108 | heap.Init(mq) 109 | 110 | return 111 | } 112 | 113 | // Migrate initializes the EDB database for the IM Operands and performs any additional migrations that may be needed. 114 | func Migrate(ctx context.Context, c chan *migration.Result, migrations *migration.MigrationQueue) { 115 | reqLogger := logf.FromContext(ctx).WithName("migration_worker") 116 | 117 | migrationCtx, cancel := context.WithCancel(ctx) 118 | defer cancel() 119 | migrationCtx = logf.IntoContext(migrationCtx, reqLogger) 120 | 121 | var err error 122 | result := &migration.Result{} 123 | 124 | for _, m := range *migrations { 125 | if len(result.Incomplete) > 0 { 126 | result.Incomplete = append(result.Incomplete, m) 127 | continue 128 | } 129 | if err = m.Run(migrationCtx); err != nil { 130 | result.Error = fmt.Errorf("failure occurred during %s: %w", m.Name, err) 131 | result.Incomplete = append(result.Incomplete, m) 132 | continue 133 | } 134 | tstz := pgtype.Timestamptz{ 135 | Time: time.Now(), 136 | Valid: true, 137 | } 138 | c := &v1schema.Changelog{ 139 | ID: m.ID, 140 | Name: m.Name, 141 | IMVersion: version.Version, 142 | InstallTime: &tstz, 143 | } 144 | _, err = m.To.LogChanges(migrationCtx, c.GetInsertSQL(), v1schema.GetNamedArgsFromRow(c)) 145 | if err != nil { 146 | result.Error = fmt.Errorf("failure occurred during logging of migration %s: %w", m.Name, err) 147 | result.Incomplete = append(result.Incomplete, m) 148 | continue 149 | } else { 150 | reqLogger.Info("Completed migration", "migrationName", m.Name) 151 | } 152 | result.Complete = append(result.Complete, m) 153 | } 154 | 155 | c <- result 156 | close(c) 157 | } 158 | -------------------------------------------------------------------------------- /database/migration/migration_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package migration 17 | 18 | import ( 19 | "container/heap" 20 | 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo/v2" 24 | . "github.com/onsi/gomega" 25 | ) 26 | 27 | func TestAPIs(t *testing.T) { 28 | RegisterFailHandler(Fail) 29 | 30 | RunSpecs(t, "MigrationQueue Suite") 31 | } 32 | 33 | var _ = Describe("MigrationQueue", func() { 34 | var ( 35 | A *Migration 36 | B *Migration 37 | C *Migration 38 | D *Migration 39 | E *Migration 40 | F *Migration 41 | G *Migration 42 | H *Migration 43 | I *Migration 44 | J *Migration 45 | ) 46 | BeforeEach(func() { 47 | A = NewMigration().Name("A").Build() 48 | B = NewMigration().Name("B").Dependencies([]*Migration{A}).Build() 49 | C = NewMigration().Name("C").Dependencies([]*Migration{B}).Build() 50 | D = NewMigration().Name("D").Dependencies([]*Migration{B}).Build() 51 | E = NewMigration().Name("E").Dependencies([]*Migration{A, D}).Build() 52 | F = NewMigration().Name("F").Dependencies([]*Migration{E}).Build() 53 | G = NewMigration().Name("G").Dependencies([]*Migration{A, E, C}).Build() 54 | 55 | // Circular dependencies 56 | H = NewMigration().Name("H").Dependencies([]*Migration{J}).Build() 57 | I = NewMigration().Name("I").Dependencies([]*Migration{H}).Build() 58 | J = NewMigration().Name("J").Dependencies([]*Migration{I}).Build() 59 | 60 | }) 61 | DescribeTable("updatePrioritiesByDependencyCount", 62 | func(mSliceFunc func() []*Migration) { 63 | mq := make(MigrationQueue, 0) 64 | heap.Init(&mq) 65 | mSlice := mSliceFunc() 66 | for _, m := range mSlice { 67 | heap.Push(&mq, m) 68 | } 69 | mq.UpdatePrioritiesByDependencyCount() 70 | previousItems := make([]*Migration, 0) 71 | for mq.Len() > 0 { 72 | Expect(mq[0].index).Should(BeNumerically(">", -1)) 73 | current := heap.Pop(&mq).(*Migration) 74 | Expect(current.index).Should(BeNumerically("==", -1)) 75 | for _, d := range current.Dependencies { 76 | Expect(d.priority).Should(BeNumerically(">", current.priority)) 77 | } 78 | previousItems = append(previousItems, current) 79 | } 80 | for i := range previousItems { 81 | if i == 0 { 82 | continue 83 | } 84 | Expect(previousItems[i].priority).Should(BeNumerically("<=", previousItems[i-1].priority)) 85 | } 86 | }, 87 | Entry("Single Migration with no dependencies", func() []*Migration { return []*Migration{A} }), 88 | Entry("One Migration depending on the other should lead to dependency being first and with higher priority", func() []*Migration { return []*Migration{A, B} }), 89 | Entry("One Migration depending on the other should lead to dependency being first and with higher priority, even if the initial order is inverted", func() []*Migration { return []*Migration{B, A} }), 90 | Entry("Multiple Migrations dependent upon another should still assign higher priorities to Migrations with more dependents", func() []*Migration { return []*Migration{A, B, C, D, E, F, G} }), 91 | ) 92 | }) 93 | -------------------------------------------------------------------------------- /database/schema/v1/migration_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package v1 17 | 18 | import ( 19 | "container/heap" 20 | 21 | "github.com/IBM/ibm-iam-operator/database/migration" 22 | "github.com/jackc/pgx/v5/pgtype" 23 | 24 | "time" 25 | 26 | . "github.com/onsi/ginkgo/v2" 27 | . "github.com/onsi/gomega" 28 | ) 29 | 30 | var _ = Describe("Migration", func() { 31 | Describe("removeInvalidUserPreferences", func() { 32 | var userPrefs []UserPreferences 33 | Context("When no LastLogin set on UserPreferences", func() { 34 | BeforeEach(func() { 35 | userPrefs = []UserPreferences{{}} 36 | }) 37 | It("filters out that entry (empty struct)", func() { 38 | filtered := removeInvalidUserPreferences(userPrefs) 39 | Expect(len(filtered)).To(Equal(0)) 40 | }) 41 | It("filters out that entry (partially-filled struct)", func() { 42 | userPrefs = append(userPrefs, UserPreferences{ 43 | UserUID: "some-id", 44 | LoginCount: 0, 45 | LastLogout: &pgtype.Timestamptz{ 46 | Time: time.Now(), 47 | }, 48 | }) 49 | filtered := removeInvalidUserPreferences(userPrefs) 50 | Expect(len(filtered)).To(Equal(0)) 51 | }) 52 | }) 53 | 54 | Context("When a LastLogin set on UserPreferences", func() { 55 | BeforeEach(func() { 56 | userPrefs = []UserPreferences{ 57 | { 58 | LastLogin: &pgtype.Timestamptz{ 59 | Time: time.Now(), 60 | }, 61 | }, 62 | } 63 | }) 64 | 65 | It("retains that entry", func() { 66 | filtered := removeInvalidUserPreferences(userPrefs) 67 | Expect(len(filtered)).To(Equal(1)) 68 | }) 69 | }) 70 | }) 71 | }) 72 | 73 | var _ = DescribeTable("xorDecode", 74 | func(encoded, decoded string, shouldError bool) { 75 | value, err := xorDecode(encoded) 76 | Expect(value).To(Equal(decoded)) 77 | if shouldError { 78 | Expect(err).To(HaveOccurred()) 79 | } else { 80 | Expect(err).ToNot(HaveOccurred()) 81 | } 82 | }, 83 | Entry("Returns the decoded string when it has the prefix", "{xor}LDo8LTor", "secret", false), 84 | Entry("Returns the decoded string when it does not have the prefix", "LDo8LTor", "secret", false), 85 | Entry("Returns an empty string when argument is empty", "", "", false), 86 | Entry("Returns error when input has malformed prefix", "{{xor}", "", true), 87 | Entry("Returns error when input with prefix is not valid base64", "{xor}&#&(*asdbasdf", "", true), 88 | Entry("Returns error when input without prefix is not valid base64", "&#&(*asdbasdf", "", true), 89 | ) 90 | 91 | var _ = Describe("MigrationQueue", func() { 92 | DescribeTable("UpdatePrioritiesByDependencyCount", 93 | func(mSliceFunc func() []*migration.Migration, orderedNames []string) { 94 | mq := make(migration.MigrationQueue, 0) 95 | heap.Init(&mq) 96 | mSlice := mSliceFunc() 97 | for _, m := range mSlice { 98 | heap.Push(&mq, m) 99 | } 100 | mq.UpdatePrioritiesByDependencyCount() 101 | i := 0 102 | for mq.Len() > 0 { 103 | current := heap.Pop(&mq).(*migration.Migration) 104 | Expect(current.Name).Should(Equal(orderedNames[i])) 105 | i++ 106 | } 107 | }, 108 | Entry("InitOperandSchemas alone", 109 | func() []*migration.Migration { 110 | return []*migration.Migration{ 111 | InitOperandSchemas, 112 | } 113 | }, []string{InitOperandSchemas.Name}), 114 | Entry("InitOperandSchemas and IncreaseOIDCUsernameSize", 115 | func() []*migration.Migration { 116 | return []*migration.Migration{ 117 | IncreaseOIDCUsernameSize, 118 | InitOperandSchemas, 119 | } 120 | }, []string{InitOperandSchemas.Name, IncreaseOIDCUsernameSize.Name}), 121 | Entry("InitOperandSchemas, IncreaseOIDCUsernameSize, CreateMetadataSchema, and MongoToEDBv1", 122 | func() []*migration.Migration { 123 | return []*migration.Migration{ 124 | IncreaseOIDCUsernameSize, 125 | MongoToEDBv1, 126 | InitOperandSchemas, 127 | CreateMetadataSchema, 128 | } 129 | }, []string{CreateMetadataSchema.Name, InitOperandSchemas.Name, IncreaseOIDCUsernameSize.Name, MongoToEDBv1.Name}), 130 | ) 131 | }) 132 | -------------------------------------------------------------------------------- /database/schema/v1/suite_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package v1 17 | 18 | import ( 19 | "testing" 20 | 21 | . "github.com/onsi/ginkgo/v2" 22 | . "github.com/onsi/gomega" 23 | ) 24 | 25 | func TestV1(t *testing.T) { 26 | RegisterFailHandler(Fail) 27 | 28 | RunSpecs(t, "v1 Suite") 29 | } 30 | -------------------------------------------------------------------------------- /database/schema/v1/version.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 IBM Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package v1 17 | 18 | const SchemaVersion string = "1.1.0" 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/IBM/ibm-iam-operator 2 | 3 | go 1.24.1 4 | 5 | require ( 6 | github.com/google/uuid v1.6.0 7 | github.com/jackc/pgx/v5 v5.5.4 8 | github.com/onsi/ginkgo/v2 v2.12.0 9 | github.com/onsi/gomega v1.27.10 10 | github.com/opdev/subreconciler v0.0.0-20230302151718-c4c8b5ec17c5 11 | github.com/openshift/api v0.0.0-20231003083825-c3f7566f6ef6 12 | github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea 13 | go.mongodb.org/mongo-driver v1.14.0 14 | k8s.io/api v0.28.2 15 | k8s.io/apimachinery v0.28.2 16 | k8s.io/client-go v0.28.1 17 | k8s.io/utils v0.0.0-20230726121419-3b25d923346b 18 | sigs.k8s.io/controller-runtime v0.16.1 19 | ) 20 | 21 | require ( 22 | github.com/beorn7/perks v1.0.1 // indirect 23 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 24 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 25 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 26 | github.com/evanphx/json-patch v5.6.0+incompatible // indirect 27 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 28 | github.com/fsnotify/fsnotify v1.6.0 // indirect 29 | github.com/go-logr/logr v1.2.4 // indirect 30 | github.com/go-logr/zapr v1.2.4 // indirect 31 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 32 | github.com/go-openapi/jsonreference v0.20.2 // indirect 33 | github.com/go-openapi/swag v0.22.3 // indirect 34 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 35 | github.com/gogo/protobuf v1.3.2 // indirect 36 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 37 | github.com/golang/protobuf v1.5.3 // indirect 38 | github.com/golang/snappy v0.0.4 // indirect 39 | github.com/google/gnostic-models v0.6.8 // indirect 40 | github.com/google/go-cmp v0.6.0 // indirect 41 | github.com/google/gofuzz v1.2.0 // indirect 42 | github.com/google/gxui v0.0.0-20151028112939-f85e0a97b3a4 // indirect 43 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect 44 | github.com/imdario/mergo v0.3.12 // indirect 45 | github.com/jackc/pgpassfile v1.0.0 // indirect 46 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 47 | github.com/josharian/intern v1.0.0 // indirect 48 | github.com/json-iterator/go v1.1.12 // indirect 49 | github.com/klauspost/compress v1.13.6 // indirect 50 | github.com/mailru/easyjson v0.7.7 // indirect 51 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 52 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 53 | github.com/modern-go/reflect2 v1.0.2 // indirect 54 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect 55 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 56 | github.com/pkg/errors v0.9.1 // indirect 57 | github.com/prometheus/client_golang v1.16.0 // indirect 58 | github.com/prometheus/client_model v0.4.0 // indirect 59 | github.com/prometheus/common v0.44.0 // indirect 60 | github.com/prometheus/procfs v0.10.1 // indirect 61 | github.com/smartystreets/goconvey v1.8.1 // indirect 62 | github.com/spf13/pflag v1.0.5 // indirect 63 | github.com/stretchr/testify v1.8.4 // indirect 64 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 65 | github.com/xdg-go/scram v1.1.2 // indirect 66 | github.com/xdg-go/stringprep v1.0.4 // indirect 67 | github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect 68 | go.uber.org/multierr v1.11.0 // indirect 69 | go.uber.org/zap v1.25.0 // indirect 70 | golang.org/x/crypto v0.37.0 // indirect 71 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect 72 | golang.org/x/net v0.39.0 // indirect 73 | golang.org/x/oauth2 v0.12.0 // indirect 74 | golang.org/x/sync v0.13.0 // indirect 75 | golang.org/x/sys v0.32.0 // indirect 76 | golang.org/x/term v0.31.0 // indirect 77 | golang.org/x/text v0.24.0 // indirect 78 | golang.org/x/time v0.3.0 // indirect 79 | golang.org/x/tools v0.23.0 // indirect 80 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 81 | google.golang.org/appengine v1.6.7 // indirect 82 | google.golang.org/protobuf v1.33.0 // indirect 83 | gopkg.in/inf.v0 v0.9.1 // indirect 84 | gopkg.in/yaml.v2 v2.4.0 // indirect 85 | gopkg.in/yaml.v3 v3.0.1 // indirect 86 | k8s.io/apiextensions-apiserver v0.28.1 // indirect 87 | k8s.io/component-base v0.28.1 // indirect 88 | k8s.io/klog/v2 v2.100.1 // indirect 89 | k8s.io/kube-openapi v0.0.0-20230905202853-d090da108d2f // indirect 90 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 91 | sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect 92 | sigs.k8s.io/yaml v1.3.0 // indirect 93 | ) 94 | 95 | replace golang.org/x/crypto => golang.org/x/crypto v0.37.0 96 | 97 | replace golang.org/x/net => golang.org/x/net v0.39.0 98 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 IBM Corporation. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /hack/bundle-render: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -u -o pipefail 4 | 5 | REPO_ROOT="" 6 | if command -v realpath &>/dev/null 7 | then 8 | REPO_ROOT="$(realpath $(dirname $0)/..)" 9 | else 10 | REPO_ROOT="$(dirname $0/..)" 11 | fi 12 | export LOCALBIN="${REPO_ROOT}/bin" 13 | YQ="${LOCALBIN}/yq" 14 | OPM="${LOCALBIN}/opm" 15 | 16 | export bundle_name="${1}" 17 | export bundle_img="${2}" 18 | 19 | rc=0 20 | ${YQ} -e \ 21 | 'select(.schema == "olm.bundle" and .name == env(bundle_name))' \ 22 | ${REPO_ROOT}/catalog/index.yml &>/dev/null || 23 | rc=$? 24 | # If the bundle is already present in the index, update the image reference used; otherwise, render from the image in 25 | # the registry 26 | if [[ $rc == 0 ]]; then 27 | ${YQ} -i \ 28 | 'with(select(.schema == "olm.bundle" and .name == env(bundle_name)); .image = env(bundle_img))' \ 29 | ${REPO_ROOT}/catalog/index.yml || 30 | rc=$? 31 | else 32 | ${OPM} render ${bundle_img} \ 33 | --output=yaml \ 34 | >> ${REPO_ROOT}/catalog/index.yml 35 | fi 36 | 37 | -------------------------------------------------------------------------------- /hack/channel-render: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -u -o pipefail -x 4 | 5 | REPO_ROOT="" 6 | if command -v realpath &>/dev/null 7 | then 8 | REPO_ROOT="$(realpath $(dirname $0)/..)" 9 | else 10 | REPO_ROOT="$(dirname $0/..)" 11 | fi 12 | export LOCALBIN="${REPO_ROOT}/bin" 13 | YQ="${LOCALBIN}/yq" 14 | 15 | export channel_name="${1}" 16 | export package_name="${2}" 17 | export bundle_version="${3}" 18 | export bundle_skip_range="${4}" 19 | export bundle_name="${package_name}.v${bundle_version}" 20 | 21 | rc=0 22 | ${YQ} -e '. | select(.schema == "olm.channel" and .name == env(channel_name) and .package == env(package_name))' ${REPO_ROOT}/catalog/index.yml &>/dev/null || 23 | rc=$? 24 | 25 | # If the channel definition lookup in the index isn't successful, append it 26 | if [[ $rc != 0 ]]; then 27 | cat >>${REPO_ROOT}/catalog/index.yml </dev/null 10 | then 11 | REPO_ROOT="$(realpath $(dirname $0)/..)" 12 | else 13 | REPO_ROOT="$(dirname $0/..)" 14 | fi 15 | export LOCALBIN="${REPO_ROOT}/bin" 16 | 17 | prod_config_dirs=( $(find "${REPO_ROOT}/config" -type d -name "prod") ) 18 | 19 | echo "Creating any missing dev overlay directories" 20 | for prod_config_dir in "${prod_config_dirs[@]}" 21 | do 22 | dev_config_dir="$(dirname "${prod_config_dir}")/dev" 23 | if [[ -d "${dev_config_dir}" ]] 24 | then 25 | echo "Overlay directory \"${dev_config_dir}\" already present, skipping" 26 | continue 27 | fi 28 | cp -r "${prod_config_dir}" "${dev_config_dir}" 29 | sed -i'.bk' -e 's%/prod%/dev%' "${dev_config_dir}/kustomization.yaml" 30 | rm "${dev_config_dir}/kustomization.yaml.bk" 31 | echo "Created dev overlay \"${dev_config_dir}/kustomization.yaml\"" 32 | done 33 | 34 | -------------------------------------------------------------------------------- /hack/external-crs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "apiVersion": "operator.ibm.com/v1alpha1", 4 | "kind": "OperandBindInfo", 5 | "metadata": { 6 | "name": "ibm-iam-bindinfo" 7 | }, 8 | "spec": { 9 | "operand": "ibm-im-operator", 10 | "registry": "common-service", 11 | "description": "Binding information that should be accessible to iam adopters", 12 | "bindings": { 13 | "public-oidc-creds": { 14 | "secret": "platform-oidc-credentials" 15 | }, 16 | "public-auth-creds": { 17 | "secret" : "platform-auth-idp-credentials" 18 | }, 19 | "public-scim-creds": { 20 | "secret" : "platform-auth-scim-credentials" 21 | }, 22 | "public-auth-cert": { 23 | "secret" : "platform-auth-secret" 24 | }, 25 | "public-cam-secret": { 26 | "secret" : "oauth-client-secret" 27 | }, 28 | "public-cam-map": { 29 | "configmap" : "oauth-client-map" 30 | }, 31 | "public-auth-config": { 32 | "configmap" : "platform-auth-idp" 33 | }, 34 | "public-ibmcloud-config": { 35 | "configmap" : "ibmcloud-cluster-info" 36 | }, 37 | "public-ibmcloudca-secret": { 38 | "secret" : "ibmcloud-cluster-ca-cert" 39 | } 40 | } 41 | } 42 | } 43 | ] 44 | 45 | -------------------------------------------------------------------------------- /hack/is_semver: -------------------------------------------------------------------------------- 1 | # /usr/bin/env bash 2 | # Credit to https://www.bashoneliners.com/oneliners/331/ 3 | # 4 | re_semver='^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$' 5 | perl -wln -e "/$re_semver/ or exit(1)" <<< "${VERSION}" 6 | -------------------------------------------------------------------------------- /hack/manager_patch.yaml: -------------------------------------------------------------------------------- 1 | configMapKeyRef: 2 | name: namespace-scope 3 | key: namespaces 4 | 5 | -------------------------------------------------------------------------------- /hack/patch-built-bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | # Hack to add in the OperandRequest/OperandBindInfo after bundle validation; bundle validation will fail if they are 5 | # included in the examples beforehand. alm-examples is a JSON string, which makes it somewhat awkward to deal with in 6 | # kustomize. Instead, use yq to get the Authentication CR example as a JSON file, merge that example with the 7 | # OperandRequest and OperandBindInfo examples, and set alm-examples to that merged result. 8 | 9 | set -e -u -o pipefail 10 | 11 | REPO_ROOT="" 12 | if command -v realpath &>/dev/null 13 | then 14 | REPO_ROOT="$(realpath $(dirname $0)/..)" 15 | else 16 | REPO_ROOT="$(dirname $0/..)" 17 | fi 18 | export LOCALBIN="${REPO_ROOT}/bin" 19 | YQ="${LOCALBIN}/yq" 20 | 21 | # Path to the bundle that needs patching 22 | POSTFIX="${1:-}" 23 | BUNDLE_DIR_PATH="${REPO_ROOT}/bundle" 24 | if [[ -n "${POSTFIX:-}" ]] 25 | then 26 | BUNDLE_DIR_PATH="${BUNDLE_DIR_PATH}-${POSTFIX}" 27 | fi 28 | 29 | CSV_PATH="${BUNDLE_DIR_PATH}/manifests/ibm-iam-operator.clusterserviceversion.yaml" 30 | ANNOTATIONS_PATH="${BUNDLE_DIR_PATH}/metadata/annotations.yaml" 31 | 32 | ${YQ} -i '.annotations."com.redhat.openshift.versions" = "v4.12-v4.18"' "${ANNOTATIONS_PATH}" 33 | # Also need to replace the WATCH_NAMESPACE value that operator-sdk seems to overwrite with a reference to the 34 | # namespace-scope ConfigMap 35 | ${YQ} -i '.spec.install.spec.deployments[].spec.template.spec.containers[].env |= map(select(.name == "WATCH_NAMESPACE").valueFrom=load("./hack/manager_patch.yaml"))' "${CSV_PATH}" 36 | # Trying to include relatedImages in the config base leads to it being clobbered by operator-sdk apparently 37 | ${YQ} -i '.spec.relatedImages = load("./hack/relatedimages.yaml")' "${CSV_PATH}" 38 | -------------------------------------------------------------------------------- /hack/relatedimages.yaml: -------------------------------------------------------------------------------- 1 | - image: icr.io/cpopen/ibm-iam-operator:4.12.0 2 | name: IBM_IAM_OPERATOR_IMAGE 3 | - image: icr.io/cpopen/cpfs/icp-platform-auth:4.12.0 4 | name: ICP_PLATFORM_AUTH_IMAGE 5 | - image: icr.io/cpopen/cpfs/icp-identity-provider:4.12.0 6 | name: ICP_IDENTITY_PROVIDER_IMAGE 7 | - image: icr.io/cpopen/cpfs/icp-identity-manager:4.12.0 8 | name: ICP_IDENTITY_MANAGER_IMAGE 9 | - image: icr.io/cpopen/cpfs/im-initcontainer:4.12.0 10 | name: IM_INITCONTAINER_IMAGE 11 | - image: icr.io/cpopen/cpfs/iam-custom-hostname:4.12.0 12 | name: IAM_CUSTOM_HOSTNAME_IMAGE 13 | -------------------------------------------------------------------------------- /hack/update_image_refs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | 4 | REPO_ROOT="" 5 | if command -v realpath &>/dev/null 6 | then 7 | REPO_ROOT="$(realpath $(dirname $0)/..)" 8 | else 9 | REPO_ROOT="$(dirname $0/..)" 10 | fi 11 | export LOCALBIN="${REPO_ROOT}/bin" 12 | 13 | overlay="${1:-dev}" 14 | 15 | function update_tags() { 16 | $LOCALBIN/yq -i '.spec.*.imageTag = env(VERSION)' \ 17 | "${REPO_ROOT}/config/samples/overlays/${overlay}/authentication_image_patch.yaml" 18 | $LOCALBIN/yq -i 'with(.[].value ; .value |= sub("([a-zA-Z0-9-./]+):([a-zA-Z0-9-.]+)", "${1}:"+env(VERSION)))' \ 19 | "${REPO_ROOT}/config/manager/overlays/${overlay}/image_env_vars_patch.yaml" 20 | $LOCALBIN/yq -i 'with(.[] | select(.name == "IBM_IAM_OPERATOR_IMAGE") ; .image |= sub("([a-zA-Z0-9-./]+):([a-zA-Z0-9-.]+)", "${1}:"+env(VERSION)))' \ 21 | "${REPO_ROOT}/hack/relatedimages.yaml" 22 | $LOCALBIN/yq -i 'with(.[] | select(.name == "IAM_CUSTOM_HOSTNAME_IMAGE") ; .image |= sub("([a-zA-Z0-9-./]+):([a-zA-Z0-9-.]+)", "${1}:"+env(VERSION)))' \ 23 | "${REPO_ROOT}/hack/relatedimages.yaml" 24 | } 25 | 26 | function update_names() { 27 | $LOCALBIN/yq -i 'with(.[] | select(.path == "/metadata/annotations/containerImage") ; .value |= sub("([a-zA-Z0-9-./]+):([a-zA-Z0-9-.]+)", env(IMAGE_TAG_BASE) + ":${2}"))' \ 28 | "${REPO_ROOT}/config/manifests/overlays/${overlay}/annotations_patch.yaml" 29 | $LOCALBIN/yq -i 'with(.[] | select(.name == "IBM_IAM_OPERATOR_IMAGE") ; .image |= sub("([a-zA-Z0-9-./]+):([a-zA-Z0-9-.]+)", env(IMAGE_TAG_BASE) + ":${2}"))' \ 30 | "${REPO_ROOT}/hack/relatedimages.yaml" 31 | } 32 | 33 | case "${overlay}" in 34 | dev) 35 | update_tags 36 | update_names 37 | ;; 38 | prod) 39 | update_tags 40 | ;; 41 | *) 42 | >&2 echo "Invalid overlay provided; : dev prod" 43 | exit 1 44 | ;; 45 | esac 46 | -------------------------------------------------------------------------------- /hack/update_operator_version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # update_operator_version writes the value stored in the VERSION environment variable to all locations where it is 4 | # needed in order to propagate to the Operator bundle. VERSION must be set to a valid SemVer, and the change is carried 5 | # across both dev and prod Kustomize overlays as well as other non-Kustomize files used for patching the CSV after 6 | # operator-sdk validation. 7 | # 8 | 9 | set -e -u -o pipefail 10 | 11 | REPO_ROOT="" 12 | if command -v realpath &>/dev/null 13 | then 14 | REPO_ROOT="$(realpath $(dirname $0)/..)" 15 | else 16 | REPO_ROOT="$(dirname $0/..)" 17 | fi 18 | export LOCALBIN="${REPO_ROOT}/bin" 19 | 20 | 21 | echo "Verify VERSION is SemVer-compliant" 22 | $REPO_ROOT/hack/is_semver || { 23 | >&2 echo "Unexpected error: VERSION=${VERSION} is not a valid SemVer; will not update version values" 24 | exit 1 25 | } 26 | 27 | 28 | export CPOPEN_LOCATION="icr.io/cpopen" 29 | export PRODUCTION_IMAGE="${CPOPEN_LOCATION}/ibm-iam-operator" 30 | 31 | 32 | echo "Update the version.go" 33 | current_version="$(grep 'Version =' "${REPO_ROOT}/version/version.go" | cut -f2 -d\")" 34 | sed s/$current_version/$VERSION/ $REPO_ROOT/version/version.go > $REPO_ROOT/version/version.go.new 35 | mv $REPO_ROOT/version/version.go.new $REPO_ROOT/version/version.go 36 | 37 | 38 | echo "Update the containerImage and olm.skipRange annotations in dev and patch" 39 | $LOCALBIN/yq -i 'with(.[] | select(.path == "/metadata/annotations/containerImage") ; .value |= env(PRODUCTION_IMAGE) + ":" + env(VERSION))' \ 40 | "${REPO_ROOT}/config/manifests/overlays/prod/annotations_patch.yaml" 41 | $LOCALBIN/yq -i 'with(.[] | select(.path == "/metadata/annotations/olm.skipRange") ; .value |= "<" + env(VERSION))' \ 42 | "${REPO_ROOT}/config/manifests/overlays/prod/annotations_patch.yaml" 43 | cp "${REPO_ROOT}/config/manifests/overlays/prod/annotations_patch.yaml" "${REPO_ROOT}/config/manifests/overlays/dev/annotations_patch.yaml" 44 | 45 | 46 | echo "Update all related images' tags" 47 | $LOCALBIN/yq -i 'with(.[] | select(.name != "IBM_IAM_OPERATOR_IMAGE"); .image |= sub("([a-zA-Z0-9-./]+)/([a-zA-Z0-9-.]+):([a-zA-Z0-9-.]+)", env(CPOPEN_LOCATION)+"/cpfs/${2}:"+env(VERSION)))' \ 48 | "${REPO_ROOT}/hack/relatedimages.yaml" 49 | $LOCALBIN/yq -i 'with(.[] | select(.name == "IBM_IAM_OPERATOR_IMAGE"); .image |= env(PRODUCTION_IMAGE)+":"+env(VERSION))' \ 50 | "${REPO_ROOT}/hack/relatedimages.yaml" 51 | 52 | 53 | echo "Update all image-related environment variables' tags" 54 | $LOCALBIN/yq -i 'with(.[].value ; .value |= sub("([a-zA-Z0-9-./]+):([a-zA-Z0-9-.]+)", "${1}:"+env(VERSION)))' \ 55 | "${REPO_ROOT}/config/manager/overlays/prod/image_env_vars_patch.yaml" 56 | cp "${REPO_ROOT}/config/manager/overlays/prod/image_env_vars_patch.yaml" "${REPO_ROOT}/config/manager/overlays/dev/image_env_vars_patch.yaml" 57 | 58 | 59 | echo "Update controller image in manager kustomization.yaml" 60 | $LOCALBIN/yq -i '.images[0].newTag = env(VERSION)' "${REPO_ROOT}/config/manager/overlays/prod/kustomization.yaml" 61 | cp "${REPO_ROOT}/config/manager/overlays/prod/kustomization.yaml" "${REPO_ROOT}/config/manager/overlays/dev/kustomization.yaml" 62 | 63 | echo "Update sample image tags" 64 | $LOCALBIN/yq -i '.spec.*.imageTag = env(VERSION)' \ 65 | "${REPO_ROOT}/config/samples/overlays/prod/authentication_image_patch.yaml" 66 | cp "${REPO_ROOT}/config/samples/overlays/prod/authentication_image_patch.yaml" "${REPO_ROOT}/config/samples/overlays/dev/authentication_image_patch.yaml" 67 | 68 | echo "Update chart versions" 69 | $LOCALBIN/yq -i '.appVersion = env(VERSION), .version = env(VERSION)' "${REPO_ROOT}/helm/Chart.yaml" 70 | $LOCALBIN/yq -i '.appVersion = env(VERSION), .version = env(VERSION)' "${REPO_ROOT}/helm-cluster-scoped/Chart.yaml" 71 | 72 | echo "Making the bundle" 73 | make bundle 74 | 75 | echo "Done" 76 | -------------------------------------------------------------------------------- /helm-cluster-scoped/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: ibm-iam-operator-cluster-scoped 3 | description: Cluster-scoped Resources for IBM IAM Operator 4 | type: application 5 | version: "4.12.0" 6 | appVersion: "4.12.0" 7 | sources: 8 | - https://github.com/IBM/ibm-iam-operator/ 9 | -------------------------------------------------------------------------------- /helm-cluster-scoped/templates/01_oidc.security.ibm.com_clients.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app.kubernetes.io/instance: ibm-iam-operator 7 | app.kubernetes.io/managed-by: ibm-iam-operator 8 | app.kubernetes.io/name: ibm-iam-operator 9 | component-id: {{ .Chart.Name }} 10 | name: clients.oidc.security.ibm.com 11 | spec: 12 | conversion: 13 | strategy: None 14 | group: oidc.security.ibm.com 15 | names: 16 | kind: Client 17 | listKind: ClientList 18 | plural: clients 19 | singular: client 20 | scope: Namespaced 21 | versions: 22 | - name: v1 23 | schema: 24 | openAPIV3Schema: 25 | description: The Client custom resource is used for registering clients with 26 | Identity Management service 27 | properties: 28 | apiVersion: 29 | description: 'APIVersion defines the versioned schema of this representation 30 | of an object. Servers should convert recognized schemas to the latest 31 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' 32 | type: string 33 | kind: 34 | description: 'Kind is a string value representing the REST resource this 35 | object represents. Servers may infer this from the endpoint the client 36 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' 37 | type: string 38 | metadata: 39 | type: object 40 | spec: 41 | type: object 42 | x-kubernetes-preserve-unknown-fields: true 43 | status: 44 | type: object 45 | x-kubernetes-preserve-unknown-fields: true 46 | type: object 47 | served: true 48 | storage: true 49 | subresources: 50 | status: {} 51 | status: 52 | acceptedNames: 53 | kind: Client 54 | listKind: ClientList 55 | plural: clients 56 | singular: client 57 | conditions: [] 58 | storedVersions: 59 | - v1 60 | -------------------------------------------------------------------------------- /helm-cluster-scoped/templates/10_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: ibm-iam-operator-{{ .Values.global.operatorNamespace }} 5 | namespace: {{ .Values.global.operatorNamespace }} 6 | labels: 7 | app.kubernetes.io/instance: ibm-iam-operator 8 | app.kubernetes.io/managed-by: ibm-iam-operator 9 | app.kubernetes.io/name: ibm-iam-operator 10 | component-id: {{ .Chart.Name }} 11 | rules: 12 | - apiGroups: 13 | - rbac.authorization.k8s.io 14 | resources: 15 | - clusterroles 16 | - clusterrolebindings 17 | verbs: 18 | - create 19 | - apiGroups: 20 | - user.openshift.io 21 | resources: 22 | - users 23 | - groups 24 | - identities 25 | verbs: 26 | - get 27 | - list 28 | -------------------------------------------------------------------------------- /helm-cluster-scoped/templates/11_clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: ibm-iam-operator-{{ .Values.global.operatorNamespace }} 5 | labels: 6 | component-id: {{ .Chart.Name }} 7 | subjects: 8 | - kind: ServiceAccount 9 | name: ibm-iam-operator 10 | namespace: {{ .Values.global.operatorNamespace }} 11 | roleRef: 12 | kind: ClusterRole 13 | name: ibm-iam-operator-{{ .Values.global.operatorNamespace }} 14 | apiGroup: rbac.authorization.k8s.io 15 | -------------------------------------------------------------------------------- /helm/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: ibm-iam-operator 3 | description: IBM IAM Operator 4 | type: application 5 | version: "4.12.0" 6 | appVersion: "4.12.0" 7 | sources: 8 | - https://github.com/IBM/ibm-iam-operator/ 9 | -------------------------------------------------------------------------------- /helm/values.yaml: -------------------------------------------------------------------------------- 1 | cpfs: 2 | imageRegistryNamespaceOperator: cpopen 3 | imageRegistryNamespaceOperand: cpopen/cpfs 4 | labels: 5 | 6 | global: 7 | operatorNamespace: operators 8 | instanceNamespace: instance 9 | imagePullPrefix: icr.io 10 | imagePullSecret: ibm-entitlement-key 11 | imagePullPolicy: IfNotPresent 12 | tetheredNamespaces: 13 | - tenant1 14 | - tenant2 15 | 16 | operator: 17 | imageTag: "4.12.0" 18 | operands: 19 | platformIdentityProvider: 20 | imageTag: "4.12.0" 21 | platformAuthService: 22 | imageTag: "4.12.0" 23 | platformIdentityManagement: 24 | imageTag: "4.12.0" 25 | imInitContainer: 26 | imageTag: "4.12.0" 27 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /testing/utils.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | . "github.com/onsi/gomega" 8 | "github.com/opdev/subreconciler" 9 | k8sErrors "k8s.io/apimachinery/pkg/api/errors" 10 | ctrl "sigs.k8s.io/controller-runtime" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | ) 13 | 14 | func ConfirmThatItRequeuesWithError(result *ctrl.Result, err error) { 15 | Expect(result).ToNot(BeNil()) 16 | Expect(result.Requeue).To(BeTrue()) 17 | Expect(result.RequeueAfter).To(BeZero()) 18 | Expect(err).To(HaveOccurred()) 19 | Expect(subreconciler.ShouldContinue(result, err)).To(BeFalse()) 20 | Expect(subreconciler.ShouldRequeue(result, err)).To(BeTrue()) 21 | Expect(subreconciler.ShouldHaltOrRequeue(result, err)).To(BeTrue()) 22 | } 23 | 24 | func ConfirmThatItRequeuesWithDelay(result *ctrl.Result, err error, expectedDelay time.Duration) { 25 | Expect(result).ToNot(BeNil()) 26 | Expect(result.Requeue).To(BeTrue()) 27 | Expect(result.RequeueAfter).To(Equal(expectedDelay)) 28 | Expect(err).ToNot(HaveOccurred()) 29 | Expect(subreconciler.ShouldContinue(result, err)).To(BeFalse()) 30 | Expect(subreconciler.ShouldRequeue(result, err)).To(BeTrue()) 31 | Expect(subreconciler.ShouldHaltOrRequeue(result, err)).To(BeTrue()) 32 | } 33 | 34 | func ConfirmThatItContinuesReconciling(result *ctrl.Result, err error) { 35 | Expect(result).To(BeNil()) 36 | Expect(err).ToNot(HaveOccurred()) 37 | Expect(subreconciler.ShouldContinue(result, err)).To(BeTrue()) 38 | Expect(subreconciler.ShouldRequeue(result, err)).To(BeFalse()) 39 | Expect(subreconciler.ShouldHaltOrRequeue(result, err)).To(BeFalse()) 40 | } 41 | 42 | type fakeErrorClient interface { 43 | client.Client 44 | Error() error 45 | } 46 | 47 | type FakeErrorClient struct { 48 | client.Client 49 | ErrFunc func() error 50 | GetAllowed bool 51 | UpdateAllowed bool 52 | CreateAllowed bool 53 | DeleteAllowed bool 54 | } 55 | 56 | var _ fakeErrorClient = &FakeErrorClient{} 57 | 58 | func (f *FakeErrorClient) Error() error { 59 | return f.ErrFunc() 60 | } 61 | 62 | func (f *FakeErrorClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { 63 | if f.GetAllowed { 64 | return f.Client.Get(ctx, key, obj, opts...) 65 | } 66 | return f.Error() 67 | } 68 | 69 | func (f *FakeErrorClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { 70 | if f.UpdateAllowed { 71 | return f.Client.Update(ctx, obj, opts...) 72 | } 73 | return f.Error() 74 | } 75 | 76 | func (f *FakeErrorClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { 77 | if f.CreateAllowed { 78 | return f.Client.Create(ctx, obj, opts...) 79 | } 80 | return f.Error() 81 | } 82 | 83 | func (f *FakeErrorClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { 84 | if f.DeleteAllowed { 85 | return f.Client.Delete(ctx, obj, opts...) 86 | } 87 | return f.Error() 88 | } 89 | 90 | func NewFakeTimeoutClient(cl client.Client) *FakeErrorClient { 91 | return &FakeErrorClient{ 92 | Client: cl, 93 | ErrFunc: func() error { 94 | return k8sErrors.NewTimeoutError("dummy error", 500) 95 | }, 96 | } 97 | } 98 | 99 | type FakeTimeoutClient struct { 100 | client.Client 101 | goodCalls int 102 | } 103 | 104 | func (f *FakeTimeoutClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { 105 | if f.goodCalls > 0 { 106 | f.goodCalls-- 107 | return f.Client.Get(ctx, key, obj, opts...) 108 | } 109 | return k8sErrors.NewTimeoutError("dummy error", 500) 110 | } 111 | 112 | func (f *FakeTimeoutClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { 113 | if f.goodCalls > 0 { 114 | f.goodCalls-- 115 | return f.Client.Update(ctx, obj, opts...) 116 | } 117 | return k8sErrors.NewTimeoutError("dummy error", 500) 118 | } 119 | 120 | func (f *FakeTimeoutClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { 121 | if f.goodCalls > 0 { 122 | f.goodCalls-- 123 | return f.Client.Create(ctx, obj, opts...) 124 | } 125 | return k8sErrors.NewTimeoutError("dummy error", 500) 126 | } 127 | 128 | func (f *FakeTimeoutClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { 129 | if f.goodCalls > 0 { 130 | f.goodCalls-- 131 | return f.Client.Delete(ctx, obj, opts...) 132 | } 133 | return k8sErrors.NewTimeoutError("dummy error", 500) 134 | } 135 | 136 | var _ client.Client = &FakeTimeoutClient{} 137 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 IBM Corporation 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package version 15 | 16 | var ( 17 | Version = "4.12.0" 18 | ) 19 | --------------------------------------------------------------------------------