diff --git a/Dockerfile b/Dockerfile index 299e0e6f..ef2bd9c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,8 +57,9 @@ ENV COMMIT_SHA=${CI_COMMIT_SHA} \ COMMIT_BRANCH=${CI_COMMIT_BRANCH} WORKDIR / COPY --from=builder /workspace/manager . -COPY ords/ords_init.sh . -COPY ords/ords_start.sh . +COPY ordssrvs/ords_init.sh /ordssrvs/ +COPY ordssrvs/ords_start.sh /ordssrvs/ +COPY ordssrvs/RSADecryptOAEP.java /ordssrvs/ COPY LICENSE.txt /licenses/ COPY THIRD_PARTY_LICENSES_DOCKER.txt /licenses/ COPY THIRD_PARTY_LICENSES.txt /licenses/ diff --git a/README.md b/README.md index da6a179e..4b92b747 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,30 @@ +># ⚠️ **OrdsSrvs Controller Development Branch** ⚠️ +> +>This is a development branch for **OrdsSrvs Controller new features**. It is not the main/stable branch and should not be used for production. +> +> [OrdsSrvs Controller](./docs/ordsservices/README.md) +> +>**CHANGELOG** +>* **Native Kubernetes Secret** +>**encPrivKey** attribute is now optional; passwords can be stored in native K8s Secrets without additional encryption. +>* **Secret Env Injection** +>Passwords are injected via Kubernetes Secrets into the init container. Secret values are not shown in kubectl describe pod output; only references (e.g., Secret names/keys) may appear. +>* **TNSAdminSecret** +>adding a dedicated documentation page. see [resources](./docs/ordsservices/examples/resources.md) +>* **poolWalletSecret** +>New attribute poolWalletSecret lets you reference an external Oracle Wallet that contains the database credentials, avoiding storage of the password in a Kubernetes Secret. +see [resources](./docs/ordsservices/examples/resources.md) +>* **central.config.url** +Deploying ORDS with Central Configuration Server (e.g. https://central-config.example.com:8585/central/v1/config) +> +>**HOWTO** +>To build your own Oracle Operator image use this: +>```bash +>make generate manifests install operator-yaml image-build IMG=\ +>``` + + + # Oracle Database Operator for Kubernetes ## Make Oracle Database Kubernetes Native @@ -13,7 +40,7 @@ As part of Oracle's resolution to make Oracle Database Kubernetes native (that i * Provision, add & delete asm disks, and more * **ORDS Service** * ServiceAccount and OpenShift support - * Auto download of APEX installation files and APEX image on a Persistent Volume + * Auto download of APEX installation files and APEX images on a Persistent Volume * **Integrations** * Private Cloud Appliance (PCA) * Compute Cloud@Customer (C3) diff --git a/apis/database/v4/ordssrvs_types.go b/apis/database/v4/ordssrvs_types.go index d7e5156a..e7df2ab9 100644 --- a/apis/database/v4/ordssrvs_types.go +++ b/apis/database/v4/ordssrvs_types.go @@ -46,37 +46,62 @@ import ( // OrdsSrvsSpec defines the desired state of OrdsSrvs // +kubebuilder:resource:shortName="ords" type OrdsSrvsSpec struct { + // Specifies the desired Kubernetes Workload //+kubebuilder:validation:Enum=Deployment;StatefulSet;DaemonSet //+kubebuilder:default=Deployment WorkloadType string `json:"workloadType,omitempty"` + // Defines the number of desired Replicas when workloadType is Deployment or StatefulSet //+kubebuilder:validation:Minimum=1 //+kubebuilder:default=1 Replicas int32 `json:"replicas,omitempty"` + // Specifies whether to restart pods when Global or Pool configurations change ForceRestart bool `json:"forceRestart,omitempty"` + // Specifies the ORDS container image //+kubecbuilder:default=container-registry.oracle.com/database/ords:latest Image string `json:"image"` + // Specifies the ORDS container image pull policy //+kubebuilder:validation:Enum=IfNotPresent;Always;Never //+kubebuilder:default=IfNotPresent ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` + // Specifies the Secret Name for pulling the ORDS container image ImagePullSecrets string `json:"imagePullSecrets,omitempty"` + // Contains settings that are configured across the entire ORDS instance. - GlobalSettings GlobalSettings `json:"globalSettings"` - // Contains settings for individual pools/databases + //+kubebuilder:default:={} + GlobalSettings GlobalSettings `json:"globalSettings,omitempty"` + // Private key EncPrivKey PasswordSecret `json:"encPrivKey,omitempty"` + + // Contains settings for individual pools/databases PoolSettings []*PoolSettings `json:"poolSettings,omitempty"` - // +k8s:openapi-gen=true + // ServiceAccount of the OrdsSrvs Pod + // +k8s:openapi-gen=true ServiceAccountName string `json:"serviceAccountName,omitempty"` + + // Central Configuration URL + CentralConfigUrl string `json:"central.config.url,omitempty"` + + // Central Configuration Wallet + CentralConfigWallet string `json:"central.config.wallet,omitempty"` + + // Specifies the Secret containing one or more wallet.zip archives (whit different names) containing connection details and credentials for the pools. + ZipWalletsSecretName string `json:"zipWalletsSecretName,omitempty"` + } type GlobalSettings struct { + + // Specifies whether the Instance API is enabled. + InstanceAPIEnabled *bool `json:"instance.api.enabled,omitempty"` + // Specifies the setting to enable or disable metadata caching. CacheMetadataEnabled *bool `json:"cache.metadata.enabled,omitempty"` @@ -344,19 +369,14 @@ type PoolSettings struct { AutoUpgradeAPEX bool `json:"autoUpgradeAPEX,omitempty"` // Specifies the name of the database user for the connection. - // For non-ADB this will default to ORDS_PUBLIC_USER // For ADBs this must be specified and not ORDS_PUBLIC_USER // If ORDS_PUBLIC_USER is specified for an ADB, the workload will fail - //+kubebuilder:default:="ORDS_PUBLIC_USER" + // db.username can be empty in case of SEPS zip wallets (Secure External Password Store, mkstore credentials, connect /@TNSALIAS) DBUsername string `json:"db.username,omitempty"` - // Specifies the password of the specified database user. - // Replaced by: DBSecret PasswordSecret `json:"dbSecret"` - // DBPassword struct{} `json:"dbPassword,omitempty"` - - // Specifies the Secret with the dbUsername and dbPassword values + // Specifies the Secret with the db password // for the connection. - DBSecret PasswordSecret `json:"db.secret"` + DBSecret PasswordSecret `json:"db.secret,omitempty"` // Specifies the username for the database account that ORDS uses for administration operations in the database. DBAdminUser string `json:"db.adminUser,omitempty"` @@ -590,10 +610,14 @@ type PoolSettings struct { // DBTnsDirectory string `json:"db.tnsDirectory,omitempty"` */ - // Specifies the Secret containing the TNS_ADMIN directory + // Specifies the Secret containing the TNS_ADMIN directory, expected file tnanames.ora // Replaces: db.tnsDirectory TNSAdminSecret *TNSAdminSecret `json:"tnsAdminSecret,omitempty"` + // Specifies the Secret containing the pool wallet directory, expected file cwallet.sso + // Replaces: db.tnsDirectory + PoolWalletSecret *PoolWalletSecret `json:"poolWalletSecret,omitempty"` + /************************************************* * Disabled /************************************************* @@ -643,13 +667,19 @@ type CertificateSecret struct { CertificateKey string `json:"key"` } -// Defines the secret containing Certificates +// Defines a secret containing tns admin folder (network/admin), e.g. tnsnames.ora type TNSAdminSecret struct { - // Specifies the name of the TNS_ADMIN Secret + // Specifies the name of the Secret SecretName string `json:"secretName"` } -// Defines the secret containing Certificates +// Defines a secret containing pool wallet, Oracle Wallet with credentials, cwallet.sso +type PoolWalletSecret struct { + // Specifies the name of the Secret + SecretName string `json:"secretName"` +} + +// Defines the secret containing wallet.zip type DBWalletSecret struct { // Specifies the name of the Database Wallet Secret SecretName string `json:"secretName"` @@ -679,6 +709,7 @@ type OrdsSrvsStatus struct { // +operator-sdk:csv:customresourcedefinitions:type=status Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` + } //+kubebuilder:object:root=true @@ -701,6 +732,7 @@ type OrdsSrvs struct { Spec OrdsSrvsSpec `json:"spec,omitempty"` Status OrdsSrvsStatus `json:"status,omitempty"` + } //+kubebuilder:object:root=true @@ -712,6 +744,7 @@ type OrdsSrvsList struct { Items []OrdsSrvs `json:"items"` } + func init() { SchemeBuilder.Register(&OrdsSrvs{}, &OrdsSrvsList{}) } diff --git a/controllers/database/ordssrvs_controller.go b/controllers/database/ordssrvs_controller.go index db831805..fd60b66a 100644 --- a/controllers/database/ordssrvs_controller.go +++ b/controllers/database/ordssrvs_controller.go @@ -40,14 +40,9 @@ package controllers import ( "context" - "crypto/rand" - "crypto/rsa" "crypto/sha256" - "crypto/x509" - "encoding/base64" "encoding/hex" "encoding/json" - "encoding/pem" "errors" "fmt" "reflect" @@ -86,8 +81,6 @@ const ( targetHTTPPortName = "pod-http-port" targetHTTPSPortName = "pod-https-port" targetMongoPortName = "pod-mongo-port" - globalConfigMapName = "settings-global" - poolConfigPreName = "settings-" // Append PoolName controllerLabelKey = "oracle.com/ords-operator-filter" controllerLabelVal = "oracle-database-operator" specHashLabel = "oracle.com/ords-operator-spec-hash" @@ -104,14 +97,7 @@ const ( typeUnsyncedORDS = "Unsynced" ) -// Definitions used in the controller -var ordsInitScript string = "" -var ordsStartScript string = "" -var ordsGlobalConfig string = "" -var APEXInstallationExternal string = "false" -// Trigger a restart of Pods on Config Changes -var RestartPods bool = false // OrdsSrvsReconciler reconciles a OrdsSrvs object type OrdsSrvsReconciler struct { @@ -119,6 +105,14 @@ type OrdsSrvsReconciler struct { Scheme *runtime.Scheme Recorder record.EventRecorder Log logr.Logger + + ordssrvsScriptsConfigMapName string + ordssrvsGlobalSettingsConfigMapName string + APEXInstallationExternal string + passwordEncryption bool + + // Trigger a restart of Pods on Config Changes + RestartPods bool } //+kubebuilder:rbac:groups=database.oracle.com,resources=ordssrvs,verbs=get;list;watch;create;update;patch;delete @@ -155,8 +149,9 @@ func (r *OrdsSrvsReconciler) SetupWithManager(mgr ctrl.Manager) error { } func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := log.FromContext(ctx) + logger := log.FromContext(ctx).WithName("Reconcile") ordssrvs := &dbapi.OrdsSrvs{} + r.RestartPods=false // Check if resource exists or was deleted if err := r.Get(ctx, req.NamespacedName, ordssrvs); err != nil { @@ -168,17 +163,34 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c return ctrl.Result{Requeue: true, RequeueAfter: time.Minute}, err } - ordsInitScript = ordssrvs.Name + "-init-script" - ordsStartScript = ordssrvs.Name + "-start-script" - ordsGlobalConfig = ordssrvs.Name + "-" + globalConfigMapName + // empty encryption key + if ordssrvs.Spec.EncPrivKey == (dbapi.PasswordSecret{}) { + r.passwordEncryption = false + logger.Info("Password Encryption key (EncPrivKey) not set") + }else{ + r.passwordEncryption = true + logger.Info("Password Encryption key (EncPrivKey) from secret "+ordssrvs.Spec.EncPrivKey.SecretName+", key "+ordssrvs.Spec.EncPrivKey.PasswordKey) + } + + // Central Configuration + if ordssrvs.Spec.CentralConfigUrl != ""{ + logger.Info("Using Central Configuration from URL : "+ ordssrvs.Spec.CentralConfigUrl) + } + + // empty Global Settings + if ordssrvs.Spec.GlobalSettings == (dbapi.GlobalSettings{}) { + logger.Info("Global Settings is empty, using default values") + } // APEXInstallationExternal // true: persistence volume // false: no persistence volume if ordssrvs.Spec.GlobalSettings.APEXInstallationPersistence.VolumeName != "" || ordssrvs.Spec.GlobalSettings.APEXInstallationPersistence.StorageClass != "" { - APEXInstallationExternal = "true" + r.APEXInstallationExternal = "true" + } else { + r.APEXInstallationExternal = "false" } - logger.Info("Setting env external_apex to " + APEXInstallationExternal) + logger.Info("Setting env external_apex to " + r.APEXInstallationExternal) // Set the status as Unknown when no status are available if len(ordssrvs.Status.Conditions) == 0 { @@ -188,19 +200,16 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } } - // ConfigMap - Init Script - if err := r.ConfigMapReconcile(ctx, ordssrvs, ordsInitScript, 0); err != nil { - logger.Error(err, "Error in ConfigMapReconcile (init-script)") - return ctrl.Result{}, err - } + r.ordssrvsScriptsConfigMapName = ordssrvs.Name + "-scripts-config-map" + r.ordssrvsGlobalSettingsConfigMapName = ordssrvs.Name + "-global-settings-config-map" - // ConfigMap - Start Script - if err := r.ConfigMapReconcile(ctx, ordssrvs, ordsStartScript, 0); err != nil { - logger.Error(err, "Error in ConfigMapReconcile (start-script)") + // ConfigMap - Scripts + if err := r.ConfigMapReconcile(ctx, ordssrvs, r.ordssrvsScriptsConfigMapName, 0); err != nil { + logger.Error(err, "Error in ConfigMapReconcile (init-script)") return ctrl.Result{}, err } - if APEXInstallationExternal == "true" { + if r.APEXInstallationExternal == "true" { // ApexInstallation PVC if err := r.ApexInstallationPVCReconcile(ctx, ordssrvs); err != nil { logger.Error(err, "Error in ApexInstallation PVC reconcile") @@ -211,7 +220,7 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } // ConfigMap - Global Settings - if err := r.ConfigMapReconcile(ctx, ordssrvs, ordsGlobalConfig, 0); err != nil { + if err := r.ConfigMapReconcile(ctx, ordssrvs, r.ordssrvsGlobalSettingsConfigMapName, 0); err != nil { logger.Error(err, "Error in ConfigMapReconcile (Global)") return ctrl.Result{}, err } @@ -220,7 +229,7 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c definedPools := make(map[string]bool) for i := 0; i < len(ordssrvs.Spec.PoolSettings); i++ { poolName := strings.ToLower(ordssrvs.Spec.PoolSettings[i].PoolName) - poolConfigMapName := ordssrvs.Name + "-" + poolConfigPreName + poolName + poolConfigMapName := ordssrvs.Name + "-cfg-pool-" + poolName if definedPools[poolConfigMapName] { return ctrl.Result{}, errors.New("poolName: " + poolName + " is not unique") } @@ -248,7 +257,7 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // } // Set the Type as Unsynced when a pod restart is required - if RestartPods { + if r.RestartPods { condition := metav1.Condition{Type: typeUnsyncedORDS, Status: metav1.ConditionTrue, Reason: "Unsynced", Message: "Configurations have changed"} if err := r.SetStatus(ctx, req, ordssrvs, condition); err != nil { return ctrl.Result{}, err @@ -276,7 +285,7 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } // Set the Type as Available when a pod restart is not required - if !RestartPods { + if !r.RestartPods { condition := metav1.Condition{Type: typeAvailableORDS, Status: metav1.ConditionTrue, Reason: "Available", Message: "Workload in Sync"} if err := r.SetStatus(ctx, req, ordssrvs, condition); err != nil { return ctrl.Result{}, err @@ -352,7 +361,7 @@ func (r *OrdsSrvsReconciler) SetStatus(ctx context.Context, req ctrl.Request, or ords.Status.HTTPPort = ords.Spec.GlobalSettings.StandaloneHTTPPort ords.Status.HTTPSPort = ords.Spec.GlobalSettings.StandaloneHTTPSPort ords.Status.MongoPort = mongoPort - ords.Status.RestartRequired = RestartPods + ords.Status.RestartRequired = r.RestartPods if err := r.Status().Update(ctx, ords); err != nil { logr.Error(err, "Failed to update Status") return err @@ -408,7 +417,7 @@ func (r *OrdsSrvsReconciler) ConfigMapReconcile(ctx context.Context, ordssrvs *d return err } logr.Info("Created: " + configMapName) - RestartPods = true + r.RestartPods = true r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Create", "ConfigMap %s Created", configMapName) // Requery for comparison if err := r.Get(ctx, types.NamespacedName{Name: configMapName, Namespace: ordssrvs.Namespace}, definedConfigMap); err != nil { @@ -423,7 +432,7 @@ func (r *OrdsSrvsReconciler) ConfigMapReconcile(ctx context.Context, ordssrvs *d return err } logr.Info("Updated: " + configMapName) - RestartPods = true + r.RestartPods = true r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Update", "ConfigMap %s Updated", configMapName) } return nil @@ -535,7 +544,7 @@ func (r *OrdsSrvsReconciler) WorkloadReconcile(ctx context.Context, req ctrl.Req return err } logr.Info("Created: " + kind) - RestartPods = false + r.RestartPods = false r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Create", "Created %s", kind) return nil @@ -559,11 +568,11 @@ func (r *OrdsSrvsReconciler) WorkloadReconcile(ctx context.Context, req ctrl.Req if err := r.Client.Update(ctx, desiredWorkload); err != nil { return err } - RestartPods = true + r.RestartPods = true r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Update", "Updated %s", kind) } - if RestartPods && ordssrvs.Spec.ForceRestart { + if r.RestartPods && ordssrvs.Spec.ForceRestart { logr.Info("Cycling: " + kind) labelsField := reflect.ValueOf(desiredWorkload).Elem().FieldByName("Spec").FieldByName("Template").FieldByName("ObjectMeta").FieldByName("Labels") if labelsField.IsValid() { @@ -574,7 +583,7 @@ func (r *OrdsSrvsReconciler) WorkloadReconcile(ctx context.Context, req ctrl.Req return err } r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Restart", "Restarted %s", kind) - RestartPods = false + r.RestartPods = false } } @@ -673,7 +682,7 @@ func selectorDefine(ords *dbapi.OrdsSrvs) metav1.LabelSelector { func (r *OrdsSrvsReconciler) podTemplateSpecDefine(ords *dbapi.OrdsSrvs, ctx context.Context, _ ctrl.Request) corev1.PodTemplateSpec { labels := getLabels(ords.Name) - specVolumes, specVolumeMounts := VolumesDefine(ctx, ords) + specVolumes, specVolumeMounts := r.VolumesDefine(ctx, ords) envPorts := []corev1.ContainerPort{ { @@ -707,7 +716,7 @@ func (r *OrdsSrvsReconciler) podTemplateSpecDefine(ords *dbapi.OrdsSrvs, ctx con Name: ords.Name + "-init", ImagePullPolicy: corev1.PullIfNotPresent, SecurityContext: securityContextDefine(), - Command: []string{"/bin/bash", "-c", ordsSABase + "/init/ords_init.sh"}, + Command: []string{"/bin/bash", "-c", ordsSABase + "/scripts/ords_init.sh"}, Env: r.envDefine(ords, true, ctx), VolumeMounts: specVolumeMounts, }}, @@ -717,9 +726,10 @@ func (r *OrdsSrvsReconciler) podTemplateSpecDefine(ords *dbapi.OrdsSrvs, ctx con ImagePullPolicy: corev1.PullIfNotPresent, SecurityContext: securityContextDefine(), Ports: envPorts, - Command: []string{"/bin/bash", "-c", ordsSABase + "/start/ords_start.sh"}, - Env: r.envDefine(ords, false, ctx), - VolumeMounts: specVolumeMounts, + Command: []string{"/bin/bash", "-c", ordsSABase + "/scripts/ords_start.sh"}, + // DEBUG mode, change to false + Env: r.envDefine(ords, true, ctx), + VolumeMounts: specVolumeMounts, }}, ServiceAccountName: ords.Spec.ServiceAccountName, }, @@ -729,99 +739,142 @@ func (r *OrdsSrvsReconciler) podTemplateSpecDefine(ords *dbapi.OrdsSrvs, ctx con } // Volumes -func VolumesDefine(ctx context.Context, ords *dbapi.OrdsSrvs) ([]corev1.Volume, []corev1.VolumeMount) { +func (r *OrdsSrvsReconciler) VolumesDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) ([]corev1.Volume, []corev1.VolumeMount) { + + logger := log.FromContext(ctx).WithName("VolumesDefine") + // Initialize the slice to hold specifications var volumes []corev1.Volume var volumeMounts []corev1.VolumeMount - // SecretHelper - secretHelperVolume := volumeBuild(ordsInitScript, "ConfigMap", 0770) - secretHelperVolumeMount := volumeMountBuild(ordsInitScript, ordsSABase+"/init", true) - volumes = append(volumes, secretHelperVolume) - volumeMounts = append(volumeMounts, secretHelperVolumeMount) + // scripts + scriptsVolume := volumeBuild(r.ordssrvsScriptsConfigMapName, r.ordssrvsScriptsConfigMapName, "ConfigMap", 0770) + scriptsVolumeMount := volumeMountBuild(r.ordssrvsScriptsConfigMapName, ordsSABase+"/scripts", true) + volumes = append(volumes, scriptsVolume) + volumeMounts = append(volumeMounts, scriptsVolumeMount) + + if r.passwordEncryption { + secretName := ordssrvs.Spec.EncPrivKey.SecretName + encryptionKeyVolume := volumeBuild(secretName, secretName, "Secret") + encryptionKeyVolumeMount := volumeMountBuild(secretName, "/opt/oracle/sa/encryptionPrivateKey/", true) - // start-script - startScriptVolume := volumeBuild(ordsStartScript, "ConfigMap", 0770) - startScriptVolumeMount := volumeMountBuild(ordsStartScript, ordsSABase+"/start", true) - volumes = append(volumes, startScriptVolume) - volumeMounts = append(volumeMounts, startScriptVolumeMount) + volumes = append(volumes, encryptionKeyVolume) + volumeMounts = append(volumeMounts, encryptionKeyVolumeMount) + } - if APEXInstallationExternal == "true" { + if r.APEXInstallationExternal == "true" { // volume for APEX installation, same optional folder as for ORDS image - apexInstallationVolume := APEXInstallationVolumeDefine(ctx, ords) + apexInstallationVolume := r.APEXInstallationVolumeDefine(ctx, ordssrvs) apexInstallationReadOnly := false apexInstallationVolumeMount := volumeMountBuild(APEXInstallationPV, APEXInstallationMount, apexInstallationReadOnly) volumes = append(volumes, apexInstallationVolume) volumeMounts = append(volumeMounts, apexInstallationVolumeMount) } + if ordssrvs.Spec.ZipWalletsSecretName != "" { + secretName := ordssrvs.Spec.ZipWalletsSecretName + logger.Info("ZipWalletsSecretName : "+secretName) + globalCertVolume := volumeBuild(secretName, secretName, "Secret") + globalCertVolumeMount := volumeMountBuild(secretName, ordsSABase+"/zipwallets/", true) + + volumes = append(volumes, globalCertVolume) + volumeMounts = append(volumeMounts, globalCertVolumeMount) + } + // Build volume specifications for globalSettings - standaloneVolume := volumeBuild("standalone", "EmptyDir") + standaloneVolume := volumeBuild("standalone", "", "EmptyDir") standaloneVolumeMount := volumeMountBuild("standalone", ordsSABase+"/config/global/standalone/", false) - globalWalletVolume := volumeBuild("sa-wallet-global", "EmptyDir") + credentialsVolume := volumeBuild("credentials", "", "EmptyDir") + credentialsVolumeMount := volumeMountBuild("credentials", ordsSABase+"/config/global/credentials/", false) + + globalWalletVolume := volumeBuild("sa-wallet-global", "", "EmptyDir") globalWalletVolumeMount := volumeMountBuild("sa-wallet-global", ordsSABase+"/config/global/wallet/", false) - globalLogVolume := volumeBuild("sa-log-global", "EmptyDir") + globalLogVolume := volumeBuild("sa-log-global", "", "EmptyDir") globalLogVolumeMount := volumeMountBuild("sa-log-global", ordsSABase+"/log/global/", false) - globalConfigVolume := volumeBuild(ordsGlobalConfig, "ConfigMap") - globalConfigVolumeMount := volumeMountBuild(ordsGlobalConfig, ordsSABase+"/config/global/", true) + globalConfigVolume := volumeBuild(r.ordssrvsGlobalSettingsConfigMapName, r.ordssrvsGlobalSettingsConfigMapName, "ConfigMap") + globalConfigVolumeMount := volumeMountBuild(r.ordssrvsGlobalSettingsConfigMapName, ordsSABase+"/config/global/", true) - globalDocRootVolume := volumeBuild("sa-doc-root", "EmptyDir") + globalDocRootVolume := volumeBuild("sa-doc-root", "", "EmptyDir") globalDocRootVolumeMount := volumeMountBuild("sa-doc-root", ordsSABase+"/config/global/doc_root/", false) - volumes = append(volumes, standaloneVolume, globalWalletVolume, globalLogVolume, globalConfigVolume, globalDocRootVolume) - volumeMounts = append(volumeMounts, standaloneVolumeMount, globalWalletVolumeMount, globalLogVolumeMount, globalConfigVolumeMount, globalDocRootVolumeMount) + volumes = append(volumes, standaloneVolume, globalWalletVolume, globalLogVolume, globalConfigVolume, globalDocRootVolume, credentialsVolume) + volumeMounts = append(volumeMounts, standaloneVolumeMount, globalWalletVolumeMount, globalLogVolumeMount, globalConfigVolumeMount, globalDocRootVolumeMount, credentialsVolumeMount) - if ords.Spec.GlobalSettings.CertSecret != nil { - globalCertVolume := volumeBuild(ords.Spec.GlobalSettings.CertSecret.SecretName, "Secret") - globalCertVolumeMount := volumeMountBuild(ords.Spec.GlobalSettings.CertSecret.SecretName, ordsSABase+"/config/certficate/", true) + if ordssrvs.Spec.GlobalSettings.CertSecret != nil { + secretName := ordssrvs.Spec.GlobalSettings.CertSecret.SecretName + globalCertVolume := volumeBuild(secretName, secretName, "Secret") + globalCertVolumeMount := volumeMountBuild(secretName, ordsSABase+"/config/certficate/", true) volumes = append(volumes, globalCertVolume) volumeMounts = append(volumeMounts, globalCertVolumeMount) } // Build volume specifications for each pool in poolSettings - definedWalletSecret := make(map[string]bool) - definedTNSSecret := make(map[string]bool) - for i := 0; i < len(ords.Spec.PoolSettings); i++ { - poolName := strings.ToLower(ords.Spec.PoolSettings[i].PoolName) + definedVolumes := make(map[string]bool) - poolWalletName := "sa-wallet-" + poolName - poolWalletVolume := volumeBuild(poolWalletName, "EmptyDir") - poolWalletVolumeMount := volumeMountBuild(poolWalletName, ordsSABase+"/config/databases/"+poolName+"/wallet/", false) + for i := 0; i < len(ordssrvs.Spec.PoolSettings); i++ { + poolName := strings.ToLower(ordssrvs.Spec.PoolSettings[i].PoolName) - poolConfigName := ords.Name + "-" + poolConfigPreName + poolName - poolConfigVolume := volumeBuild(poolConfigName, "ConfigMap") + // /opt/oracle/sa/config/databases/POOL/ + poolConfigName := ordssrvs.Name+"-cfg-pool-" + poolName + poolConfigVolume := volumeBuild(poolConfigName, poolConfigName, "ConfigMap") poolConfigVolumeMount := volumeMountBuild(poolConfigName, ordsSABase+"/config/databases/"+poolName+"/", true) + volumes = append(volumes, poolConfigVolume) + volumeMounts = append(volumeMounts, poolConfigVolumeMount) + + // PoolWalletSecret -> /opt/oracle/sa/config/databases/POOL/wallet/ + poolWalletVolumeName := ordssrvs.Name+"-pool-wallet-" + poolName + poolWalletVolumePath := ordsSABase + "/config/databases/" + poolName + "/wallet/" + + if ( ( ordssrvs.Spec.PoolSettings[i].PoolWalletSecret == nil ) ){ + poolWalletVolume := volumeBuild(poolWalletVolumeName, poolWalletVolumeName, "EmptyDir") + poolWalletVolumeMount := volumeMountBuild(poolWalletVolumeName, poolWalletVolumePath, false) + volumes = append(volumes, poolWalletVolume) + volumeMounts = append(volumeMounts, poolWalletVolumeMount) + } else { + poolWalletSecretName := ordssrvs.Spec.PoolSettings[i].PoolWalletSecret.SecretName + if !definedVolumes[poolWalletVolumeName] { + poolWalletVolume := volumeBuild(poolWalletVolumeName, poolWalletSecretName, "Secret") + volumes = append(volumes, poolWalletVolume) + definedVolumes[poolWalletVolumeName] = true + } + poolWalletVolumeMount := volumeMountBuild(poolWalletVolumeName, poolWalletVolumePath, true) + volumeMounts = append(volumeMounts, poolWalletVolumeMount) + } - volumes = append(volumes, poolWalletVolume, poolConfigVolume) - volumeMounts = append(volumeMounts, poolWalletVolumeMount, poolConfigVolumeMount) - - if ords.Spec.PoolSettings[i].DBWalletSecret != nil { - walletSecretName := ords.Spec.PoolSettings[i].DBWalletSecret.SecretName - if !definedWalletSecret[walletSecretName] { - // Only create the volume once - poolDBWalletVolume := volumeBuild(walletSecretName, "Secret") + // DBWalletSecret -> /opt/oracle/sa/config/databases/POOL/network/admin/ + if ordssrvs.Spec.PoolSettings[i].DBWalletSecret != nil { + dbWalletSecretName := ordssrvs.Spec.PoolSettings[i].DBWalletSecret.SecretName + volumeName := ordssrvs.Name + "-pool-zipwallet-" + poolName + if !definedVolumes[volumeName] { + poolDBWalletVolume := volumeBuild(volumeName, dbWalletSecretName, "Secret") volumes = append(volumes, poolDBWalletVolume) - definedWalletSecret[walletSecretName] = true + definedVolumes[volumeName] = true } - poolDBWalletVolumeMount := volumeMountBuild(walletSecretName, ordsSABase+"/config/databases/"+poolName+"/network/admin/", true) + poolDBWalletVolumeMount := volumeMountBuild(volumeName, ordsSABase+"/config/databases/"+poolName+"/network/admin/", true) volumeMounts = append(volumeMounts, poolDBWalletVolumeMount) } - if ords.Spec.PoolSettings[i].TNSAdminSecret != nil { - tnsSecretName := ords.Spec.PoolSettings[i].TNSAdminSecret.SecretName - if !definedTNSSecret[tnsSecretName] { - // Only create the volume once - poolTNSAdminVolume := volumeBuild(tnsSecretName, "Secret") - volumes = append(volumes, poolTNSAdminVolume) - definedTNSSecret[tnsSecretName] = true + // TNSAdminSecret -> /opt/oracle/sa/config/databases/POOL/network/admin/ + if ordssrvs.Spec.PoolSettings[i].TNSAdminSecret != nil { + if ordssrvs.Spec.PoolSettings[i].DBWalletSecret == nil { + tnsSecretName := ordssrvs.Spec.PoolSettings[i].TNSAdminSecret.SecretName + poolTNSAdminVolumeName := ordssrvs.Name + "-pool-netadmin-" + poolName + if !definedVolumes[poolTNSAdminVolumeName] { + poolTNSAdminVolume := volumeBuild(poolTNSAdminVolumeName, tnsSecretName, "Secret") + volumes = append(volumes, poolTNSAdminVolume) + definedVolumes[poolTNSAdminVolumeName] = true + } + poolTNSAdminVolumeMount := volumeMountBuild(poolTNSAdminVolumeName, ordsSABase+"/config/databases/"+poolName+"/network/admin/", true) + volumeMounts = append(volumeMounts, poolTNSAdminVolumeMount) + } else { + logger.Info("Attribute TNSAdminSecret ignored, using DBWalletSecret for pool " + poolName) } - poolTNSAdminVolumeMount := volumeMountBuild(tnsSecretName, ordsSABase+"/config/databases/"+poolName+"/network/admin/", true) - volumeMounts = append(volumeMounts, poolTNSAdminVolumeMount) } + } return volumes, volumeMounts } @@ -834,7 +887,7 @@ func volumeMountBuild(name string, path string, readOnly bool) corev1.VolumeMoun } } -func volumeBuild(name string, source string, mode ...int32) corev1.Volume { +func volumeBuild(volumeName string, name string, source string, mode ...int32) corev1.Volume { defaultMode := int32(0660) if len(mode) > 0 { defaultMode = mode[0] @@ -842,7 +895,7 @@ func volumeBuild(name string, source string, mode ...int32) corev1.Volume { switch source { case "ConfigMap": return corev1.Volume{ - Name: name, + Name: volumeName, VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ DefaultMode: &defaultMode, @@ -854,7 +907,7 @@ func volumeBuild(name string, source string, mode ...int32) corev1.Volume { } case "Secret": return corev1.Volume{ - Name: name, + Name: volumeName, VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ SecretName: name, @@ -863,7 +916,7 @@ func volumeBuild(name string, source string, mode ...int32) corev1.Volume { } case "EmptyDir": return corev1.Volume{ - Name: name, + Name: volumeName, VolumeSource: corev1.VolumeSource{ EmptyDir: &corev1.EmptyDirVolumeSource{}, }, @@ -874,8 +927,8 @@ func volumeBuild(name string, source string, mode ...int32) corev1.Volume { } // Service -func (r *OrdsSrvsReconciler) ServiceDefine(ctx context.Context, ords *dbapi.OrdsSrvs, HTTPport int32, HTTPSport int32, MongoPort int32) *corev1.Service { - labels := getLabels(ords.Name) +func (r *OrdsSrvsReconciler) ServiceDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs, HTTPport int32, HTTPSport int32, MongoPort int32) *corev1.Service { + labels := getLabels(ordssrvs.Name) servicePorts := []corev1.ServicePort{ { @@ -892,7 +945,7 @@ func (r *OrdsSrvsReconciler) ServiceDefine(ctx context.Context, ords *dbapi.Ords }, } - if ords.Spec.GlobalSettings.MongoEnabled { + if ordssrvs.Spec.GlobalSettings.MongoEnabled { mongoServicePort := corev1.ServicePort{ Name: serviceMongoPortName, Protocol: corev1.ProtocolTCP, @@ -902,7 +955,7 @@ func (r *OrdsSrvsReconciler) ServiceDefine(ctx context.Context, ords *dbapi.Ords servicePorts = append(servicePorts, mongoServicePort) } - objectMeta := objectMetaDefine(ords, ords.Name) + objectMeta := objectMetaDefine(ordssrvs, ordssrvs.Name) def := &corev1.Service{ ObjectMeta: objectMeta, Spec: corev1.ServiceSpec{ @@ -912,7 +965,7 @@ func (r *OrdsSrvsReconciler) ServiceDefine(ctx context.Context, ords *dbapi.Ords } // Set the ownerRef - if err := ctrl.SetControllerReference(ords, def, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(ordssrvs, def, r.Scheme); err != nil { return nil } return def @@ -953,17 +1006,65 @@ func addEnvVar(envVars []corev1.EnvVar, name string, value string) []corev1.EnvV return append(envVars, newEnvVar) } +func (r *OrdsSrvsReconciler) addSecretEnvVar(envVars []corev1.EnvVar, ordssrvs *dbapi.OrdsSrvs, envName string, secretName string, secretKey string, ctx context.Context) []corev1.EnvVar { + + logger := log.FromContext(ctx).WithName("addSecretEnvVar") + message := fmt.Sprintf("Setting Secret env variable '%s' from secret '%s', key '%s' ", envName, secretName, secretKey) + logger.Info(message) + + // check secret exists + var secret corev1.Secret + err := r.Client.Get(ctx, client.ObjectKey{Namespace: ordssrvs.Namespace, Name: secretName}, &secret) + if err != nil { + logger.Error(err, "Secret not found "+secretName) + return envVars + } + + // check key exists + if _, exists := secret.Data[secretKey]; !exists { + logger.Info("Secret key " + secretKey + " not found for secret " + secretName) + return envVars + } + + newEnvVar := corev1.EnvVar{ + Name: envName, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: secretName, + }, + Key: secretKey, + }, + }, + } + + return append(envVars, newEnvVar) +} + // Sets environment variables in the containers func (r *OrdsSrvsReconciler) envDefine(ordssrvs *dbapi.OrdsSrvs, initContainer bool, ctx context.Context) []corev1.EnvVar { logger := log.FromContext(ctx).WithName("envDefine") envVars := []corev1.EnvVar{} + // Central Configuration + if ordssrvs.Spec.CentralConfigUrl != "" { + envVars = addEnvVar(envVars, "central_config_url", ordssrvs.Spec.CentralConfigUrl) + envVars = addEnvVar(envVars, "central_config_wallet", ordssrvs.Spec.CentralConfigWallet) + } + // ORDS_CONFIG ORDS_CONFIG := ordsSABase + "/config" logger.Info("Setting ORDS_CONFIG to " + ORDS_CONFIG) envVars = addEnvVar(envVars, "ORDS_CONFIG", ORDS_CONFIG) + // adding info for private key + if r.passwordEncryption && initContainer { + passwordKey := ordssrvs.Spec.EncPrivKey.PasswordKey + logger.Info("Setting ENC_PRV_KEY env variable to " + passwordKey) + envVars = addEnvVar(envVars, "ENC_PRV_KEY", passwordKey) + } + // avoid Java warning about JAVA_TOOL_OPTIONS envVars = addEnvVar(envVars, "JAVA_TOOL_OPTIONS", "-Doracle.ml.version_check=false") @@ -974,32 +1075,73 @@ func (r *OrdsSrvsReconciler) envDefine(ordssrvs *dbapi.OrdsSrvs, initContainer b envVars = addEnvVar(envVars, "TNS_ADMIN", tnsAdmin) } - // passwords are set for the init container only + // init container only if initContainer { + envVars = addEnvVar(envVars, "download_apex", strconv.FormatBool(ordssrvs.Spec.GlobalSettings.APEXDownload)) envVars = addEnvVar(envVars, "download_url_apex", ordssrvs.Spec.GlobalSettings.APEXDownloadUrl) - envVars = addEnvVar(envVars, "external_apex", APEXInstallationExternal) + envVars = addEnvVar(envVars, "external_apex", r.APEXInstallationExternal) + // passwords are set for the init container only for i := 0; i < len(ordssrvs.Spec.PoolSettings); i++ { poolName := strings.ReplaceAll(strings.ToLower(ordssrvs.Spec.PoolSettings[i].PoolName), "-", "_") + logger.Info("Preparing env for pool " + poolName) - // dbpassword - secretName := poolName + "_dbpassword" - secretValue := r.CommonDecryptWithPrivKey3(ordssrvs, ordssrvs.Spec.PoolSettings[i].DBSecret.SecretName, ordssrvs.Spec.PoolSettings[i].DBSecret.PasswordKey, ctx) - envVars = addEnvVar(envVars, secretName, secretValue) + // dbconnectiontype + // if set the init container will test the connection + // if not set and provided by Central Configuration, init script will skip the connection test + if ordssrvs.Spec.PoolSettings[i].DBConnectionType != "" { + envVars = addEnvVar(envVars, poolName + "_dbconnectiontype", ordssrvs.Spec.PoolSettings[i].DBConnectionType) + } + + // dbWalletZip + if ordssrvs.Spec.PoolSettings[i].DBConnectionType == "" && ordssrvs.Spec.PoolSettings[i].DBWalletZipService != "" { + envVars = addEnvVar(envVars, poolName + "_dbconnectiontype", "dbWalletZip") + } - // dbadminuserpassword - if ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.SecretName != "" { + // dbusername + if ordssrvs.Spec.PoolSettings[i].DBUsername != "" { + envVars = addEnvVar(envVars, poolName + "_dbusername", ordssrvs.Spec.PoolSettings[i].DBUsername) + + // dbpassword + envName := poolName + "_dbpassword" + // it can be provided by a wallet + if ordssrvs.Spec.PoolSettings[i].DBSecret.SecretName != "" { + secretName := ordssrvs.Spec.PoolSettings[i].DBSecret.SecretName + secretKey := ordssrvs.Spec.PoolSettings[i].DBSecret.PasswordKey + envVars = r.addSecretEnvVar(envVars, ordssrvs, envName, secretName, secretKey, ctx) + } + + } + + // dbadminuser + if ordssrvs.Spec.PoolSettings[i].DBAdminUser != "" { + envVars = addEnvVar(envVars, poolName + "_dbadminuser", ordssrvs.Spec.PoolSettings[i].DBAdminUser) + // autoupgrade only if dbAdminUser provided envVars = addEnvVar(envVars, poolName+"_autoupgrade_ords", strconv.FormatBool(ordssrvs.Spec.PoolSettings[i].AutoUpgradeORDS)) envVars = addEnvVar(envVars, poolName+"_autoupgrade_apex", strconv.FormatBool(ordssrvs.Spec.PoolSettings[i].AutoUpgradeAPEX)) - dbAdminUserPassword := r.CommonDecryptWithPrivKey3(ordssrvs, ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.SecretName, ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.PasswordKey, ctx) - envVars = addEnvVar(envVars, poolName+"_dbadminuserpassword", dbAdminUserPassword) + + // dbadminuserpassword + if ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.SecretName != "" { + envName := poolName + "_dbadminuserpassword" + secretName := ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.SecretName + secretKey := ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.PasswordKey + envVars = r.addSecretEnvVar(envVars, ordssrvs, envName, secretName, secretKey, ctx) + } + } - // dbcdbadminuserpassword - if ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.SecretName != "" { - dbCDBAdminUserPassword := r.CommonDecryptWithPrivKey3(ordssrvs, ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.SecretName, ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.PasswordKey, ctx) - envVars = addEnvVar(envVars, poolName+"_dbcdbadminuserpassword", dbCDBAdminUserPassword) + // dbcdbadminuser + if ordssrvs.Spec.PoolSettings[i].DBCDBAdminUser != "" { + envVars = addEnvVar(envVars, poolName + "_dbcdbadminuser", ordssrvs.Spec.PoolSettings[i].DBCDBAdminUser) + + // dbcdbadminuserpassword + if ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.SecretName != "" { + envName := poolName + "_dbcdbadminuserpassword" + secretName := ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.SecretName + secretKey := ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.PasswordKey + envVars = r.addSecretEnvVar(envVars, ordssrvs, envName, secretName, secretKey, ctx) + } } } } @@ -1007,11 +1149,11 @@ func (r *OrdsSrvsReconciler) envDefine(ordssrvs *dbapi.OrdsSrvs, initContainer b return envVars } -func APEXInstallationVolumeDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) corev1.Volume { +func (r *OrdsSrvsReconciler) APEXInstallationVolumeDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) corev1.Volume { logger := log.FromContext(ctx).WithName("APEXInstallationVolumeDefine") var vs corev1.VolumeSource - if APEXInstallationExternal == "false" { + if r.APEXInstallationExternal == "false" { vs = corev1.VolumeSource{ EmptyDir: &corev1.EmptyDirVolumeSource{}, } @@ -1082,34 +1224,34 @@ func (r *OrdsSrvsReconciler) APEXInstallationPVCDefine(ctx context.Context, ords /************************************************* * Deletions **************************************************/ -func (r *OrdsSrvsReconciler) ConfigMapDelete(ctx context.Context, req ctrl.Request, ords *dbapi.OrdsSrvs, definedPools map[string]bool) (err error) { +func (r *OrdsSrvsReconciler) ConfigMapDelete(ctx context.Context, req ctrl.Request, ordssrvs *dbapi.OrdsSrvs, definedPools map[string]bool) (err error) { // Delete Undefined Pool ConfigMaps configMapList := &corev1.ConfigMapList{} if err := r.List(ctx, configMapList, client.InNamespace(req.Namespace), client.MatchingLabels(map[string]string{ controllerLabelKey: controllerLabelVal, - "app.kubernetes.io/instance": ords.Name}), + "app.kubernetes.io/instance": ordssrvs.Name}), ); err != nil { return err } for _, configMap := range configMapList.Items { - if configMap.Name == ordsGlobalConfig || configMap.Name == ordsInitScript || configMap.Name == ordsStartScript { + if configMap.Name == r.ordssrvsGlobalSettingsConfigMapName || configMap.Name == r.ordssrvsScriptsConfigMapName { continue } if _, exists := definedPools[configMap.Name]; !exists { if err := r.Delete(ctx, &configMap); err != nil { return err } - RestartPods = ords.Spec.ForceRestart - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Delete", "ConfigMap %s Deleted", configMap.Name) + r.RestartPods = ordssrvs.Spec.ForceRestart + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Delete", "ConfigMap %s Deleted", configMap.Name) } } return nil } -func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Request, ords *dbapi.OrdsSrvs, kind string) (err error) { +func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Request, ordssrvs *dbapi.OrdsSrvs, kind string) (err error) { logr := log.FromContext(ctx).WithName("WorkloadDelete") // Get Workloads @@ -1117,7 +1259,7 @@ func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Reques if err := r.List(ctx, deploymentList, client.InNamespace(req.Namespace), client.MatchingLabels(map[string]string{ controllerLabelKey: controllerLabelVal, - "app.kubernetes.io/instance": ords.Name}), + "app.kubernetes.io/instance": ordssrvs.Name}), ); err != nil { return err } @@ -1126,7 +1268,7 @@ func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Reques if err := r.List(ctx, statefulSetList, client.InNamespace(req.Namespace), client.MatchingLabels(map[string]string{ controllerLabelKey: controllerLabelVal, - "app.kubernetes.io/instance": ords.Name}), + "app.kubernetes.io/instance": ordssrvs.Name}), ); err != nil { return err } @@ -1135,7 +1277,7 @@ func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Reques if err := r.List(ctx, daemonSetList, client.InNamespace(req.Namespace), client.MatchingLabels(map[string]string{ controllerLabelKey: controllerLabelVal, - "app.kubernetes.io/instance": ords.Name}), + "app.kubernetes.io/instance": ordssrvs.Name}), ); err != nil { return err } @@ -1147,14 +1289,14 @@ func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Reques return err } logr.Info("Deleted: " + kind) - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) } for _, deleteDeployment := range deploymentList.Items { if err := r.Delete(ctx, &deleteDeployment); err != nil { return err } logr.Info("Deleted: " + kind) - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) } case "DaemonSet": for _, deleteDeployment := range deploymentList.Items { @@ -1162,14 +1304,14 @@ func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Reques return err } logr.Info("Deleted: " + kind) - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) } for _, deleteStatefulSet := range statefulSetList.Items { if err := r.Delete(ctx, &deleteStatefulSet); err != nil { return err } logr.Info("Deleted StatefulSet: " + deleteStatefulSet.Name) - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) } default: for _, deleteStatefulSet := range statefulSetList.Items { @@ -1177,14 +1319,14 @@ func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Reques return err } logr.Info("Deleted: " + kind) - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) } for _, deleteDaemonSet := range daemonSetList.Items { if err := r.Delete(ctx, &deleteDaemonSet); err != nil { return err } logr.Info("Deleted: " + kind) - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) } } return nil @@ -1195,6 +1337,7 @@ func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Reques **************************************************/ func getLabels(name string) map[string]string { return map[string]string{ + "app": name, "app.kubernetes.io/instance": name, controllerLabelKey: controllerLabelVal, } @@ -1217,76 +1360,3 @@ func generateSpecHash(spec interface{}) string { return hashString } - -func CommonDecryptWithPrivKey(Key string, Buffer string) (string, error) { - - Debug := 0 - block, _ := pem.Decode([]byte(Key)) - pkcs8PrivateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) - if err != nil { - fmt.Printf("Failed to parse private key %s \n", err.Error()) - return "", err - } - if Debug == 1 { - fmt.Printf("======================================\n") - fmt.Printf("%s\n", Key) - fmt.Printf("======================================\n") - } - - encString64, err := base64.StdEncoding.DecodeString(string(Buffer)) - if err != nil { - fmt.Printf("Failed to decode encrypted string to base64: %s\n", err.Error()) - return "", err - } - - if Debug == 1 { - fmt.Printf("======================================\n") - fmt.Printf("%s\n", encString64) - fmt.Printf("======================================\n") - } - - decryptedB, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, pkcs8PrivateKey.(*rsa.PrivateKey), encString64, nil) - if err != nil { - fmt.Printf("Failed to decrypt string %s\n", err.Error()) - return "", err - } - if Debug == 1 { - fmt.Printf("[%s]\n", string(decryptedB)) - } - return strings.TrimSpace(string(decryptedB)), err - -} - -func (r *OrdsSrvsReconciler) CommonDecryptWithPrivKey3(ords *dbapi.OrdsSrvs, sname string, skey string, ctx context.Context) string { - logr := log.FromContext(ctx).WithName("CommonDecryptWithPrivKey2") - secret_par := &corev1.Secret{} - fmt.Printf("sname: %s\n", sname) - fmt.Printf("skey: %s\n", skey) - err := r.Get(ctx, types.NamespacedName{Name: sname, Namespace: ords.Namespace}, secret_par) - if err != nil { - logr.Error(err, "Cannot read secret"+sname) - return "" - } - encVal := string(secret_par.Data[skey]) - encVal = strings.TrimSpace(encVal) - - secret_key := &corev1.Secret{} - /* get private key */ - if err := r.Get(ctx, types.NamespacedName{Name: ords.Spec.EncPrivKey.SecretName, - Namespace: ords.Namespace}, secret_key); err != nil { - logr.Error(err, "Cannot get privte key") - return "" - } - PrvKeyVal := string(secret_key.Data[ords.Spec.EncPrivKey.PasswordKey]) - PrvKeyVal = strings.TrimSpace(PrvKeyVal) - - decVal, err := CommonDecryptWithPrivKey(PrvKeyVal, encVal) - if err != nil { - logr.Error(err, "Fail to decrypt secret") - return "" - } - - logr.Info("Password decryption completed") - - return decVal -} diff --git a/controllers/database/ordssrvs_ordsconfig.go b/controllers/database/ordssrvs_ordsconfig.go index 5cd29536..b7db0222 100644 --- a/controllers/database/ordssrvs_ordsconfig.go +++ b/controllers/database/ordssrvs_ordsconfig.go @@ -52,91 +52,88 @@ import ( ctrllog "sigs.k8s.io/controller-runtime/pkg/log" ) -func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ords *dbapi.OrdsSrvs, configMapName string, poolIndex int) *corev1.ConfigMap { +func readScript(ctx context.Context, filePath string) string { + log := ctrllog.FromContext(ctx).WithName("readScript") - log := ctrllog.FromContext(ctx).WithName("ConfigMapDefine") + // Read the file from controller's filesystem + scriptData, err := os.ReadFile(filePath) + if err != nil { + log.Error(err, "Error reading "+filePath) + return "error" + } + + return string(scriptData) +} + +func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs, configMapName string, poolIndex int) *corev1.ConfigMap { + + //log := ctrllog.FromContext(ctx).WithName("ConfigMapDefine") var defData map[string]string switch configMapName { - case ords.Name + "-init-script": - // Read the file from controller's filesystem - filePath := "/ords_init.sh" - scriptData, err := os.ReadFile(filePath) - if err != nil { - log.Error(err, "Error reading /ords_init.sh") - return nil - } - log.Info("adding ords_init.sh") - defData = map[string]string{ - "ords_init.sh": string(scriptData)} - case ords.Name + "-start-script": - // Read the file from controller's filesystem - filePath := "/ords_start.sh" - scriptData, err := os.ReadFile(filePath) - if err != nil { - log.Error(err, "Error reading /ords_start.sh") - return nil - } - log.Info("adding ords_start.sh") - defData = map[string]string{ - "ords_start.sh": string(scriptData)} - case ords.Name + "-" + globalConfigMapName: + case r.ordssrvsScriptsConfigMapName: + defData = make(map[string]string) + defData["ords_init.sh"] = readScript(ctx, "/ordssrvs/ords_init.sh") + defData["ords_start.sh"] = readScript(ctx, "/ordssrvs/ords_start.sh") + defData["RSADecryptOAEP.java"] = readScript(ctx, "/ordssrvs/RSADecryptOAEP.java") + case r.ordssrvsGlobalSettingsConfigMapName: // GlobalConfigMap var defStandaloneAccessLog string - if ords.Spec.GlobalSettings.EnableStandaloneAccessLog { + if ordssrvs.Spec.GlobalSettings.EnableStandaloneAccessLog { defStandaloneAccessLog = ` ` + ordsSABase + `/log/global` + "\n" } var defMongoAccessLog string - if ords.Spec.GlobalSettings.EnableMongoAccessLog { + if ordssrvs.Spec.GlobalSettings.EnableMongoAccessLog { defMongoAccessLog = ` ` + ordsSABase + `/log/global` + "\n" } var defCert string - if ords.Spec.GlobalSettings.CertSecret != nil { - defCert = ` ` + ordsSABase + `/config/certficate/` + ords.Spec.GlobalSettings.CertSecret.Certificate + `` + "\n" + - ` ` + ordsSABase + `/config/certficate/` + ords.Spec.GlobalSettings.CertSecret.CertificateKey + `` + "\n" + if ordssrvs.Spec.GlobalSettings.CertSecret != nil { + defCert = ` ` + ordsSABase + `/config/certficate/` + ordssrvs.Spec.GlobalSettings.CertSecret.Certificate + `` + "\n" + + ` ` + ordsSABase + `/config/certficate/` + ordssrvs.Spec.GlobalSettings.CertSecret.CertificateKey + `` + "\n" } defData = map[string]string{ "settings.xml": fmt.Sprint(`` + "\n" + `` + "\n" + `` + "\n" + - conditionalEntry("cache.metadata.graphql.expireAfterAccess", ords.Spec.GlobalSettings.CacheMetadataGraphQLExpireAfterAccess) + - conditionalEntry("cache.metadata.jwks.enabled", ords.Spec.GlobalSettings.CacheMetadataJWKSEnabled) + - conditionalEntry("cache.metadata.jwks.initialCapacity", ords.Spec.GlobalSettings.CacheMetadataJWKSInitialCapacity) + - conditionalEntry("cache.metadata.jwks.maximumSize", ords.Spec.GlobalSettings.CacheMetadataJWKSMaximumSize) + - conditionalEntry("cache.metadata.jwks.expireAfterAccess", ords.Spec.GlobalSettings.CacheMetadataJWKSExpireAfterAccess) + - conditionalEntry("cache.metadata.jwks.expireAfterWrite", ords.Spec.GlobalSettings.CacheMetadataJWKSExpireAfterWrite) + - conditionalEntry("database.api.management.services.disabled", ords.Spec.GlobalSettings.DatabaseAPIManagementServicesDisabled) + - conditionalEntry("db.invalidPoolTimeout", ords.Spec.GlobalSettings.DBInvalidPoolTimeout) + - conditionalEntry("feature.graphql.max.nesting.depth", ords.Spec.GlobalSettings.FeatureGraphQLMaxNestingDepth) + - conditionalEntry("request.traceHeaderName", ords.Spec.GlobalSettings.RequestTraceHeaderName) + - conditionalEntry("security.credentials.attempts", ords.Spec.GlobalSettings.SecurityCredentialsAttempts) + - conditionalEntry("security.credentials.lock.time", ords.Spec.GlobalSettings.SecurityCredentialsLockTime) + - conditionalEntry("standalone.context.path", ords.Spec.GlobalSettings.StandaloneContextPath) + - conditionalEntry("standalone.http.port", ords.Spec.GlobalSettings.StandaloneHTTPPort) + - conditionalEntry("standalone.https.host", ords.Spec.GlobalSettings.StandaloneHTTPSHost) + - conditionalEntry("standalone.https.port", ords.Spec.GlobalSettings.StandaloneHTTPSPort) + - conditionalEntry("standalone.stop.timeout", ords.Spec.GlobalSettings.StandaloneStopTimeout) + - conditionalEntry("cache.metadata.timeout", ords.Spec.GlobalSettings.CacheMetadataTimeout) + - conditionalEntry("cache.metadata.enabled", ords.Spec.GlobalSettings.CacheMetadataEnabled) + - conditionalEntry("database.api.enabled", ords.Spec.GlobalSettings.DatabaseAPIEnabled) + - conditionalEntry("debug.printDebugToScreen", ords.Spec.GlobalSettings.DebugPrintDebugToScreen) + - conditionalEntry("error.responseFormat", ords.Spec.GlobalSettings.ErrorResponseFormat) + - conditionalEntry("icap.port", ords.Spec.GlobalSettings.ICAPPort) + - conditionalEntry("icap.secure.port", ords.Spec.GlobalSettings.ICAPSecurePort) + - conditionalEntry("icap.server", ords.Spec.GlobalSettings.ICAPServer) + - conditionalEntry("log.procedure", ords.Spec.GlobalSettings.LogProcedure) + - conditionalEntry("mongo.enabled", ords.Spec.GlobalSettings.MongoEnabled) + - conditionalEntry("mongo.port", ords.Spec.GlobalSettings.MongoPort) + - conditionalEntry("mongo.idle.timeout", ords.Spec.GlobalSettings.MongoIdleTimeout) + - conditionalEntry("mongo.op.timeout", ords.Spec.GlobalSettings.MongoOpTimeout) + - conditionalEntry("security.disableDefaultExclusionList", ords.Spec.GlobalSettings.SecurityDisableDefaultExclusionList) + - conditionalEntry("security.exclusionList", ords.Spec.GlobalSettings.SecurityExclusionList) + - conditionalEntry("security.inclusionList", ords.Spec.GlobalSettings.SecurityInclusionList) + - conditionalEntry("security.maxEntries", ords.Spec.GlobalSettings.SecurityMaxEntries) + - conditionalEntry("security.verifySSL", ords.Spec.GlobalSettings.SecurityVerifySSL) + - conditionalEntry("security.httpsHeaderCheck", ords.Spec.GlobalSettings.SecurityHTTPSHeaderCheck) + - conditionalEntry("security.forceHTTPS", ords.Spec.GlobalSettings.SecurityForceHTTPS) + - conditionalEntry("externalSessionTrustedOrigins", ords.Spec.GlobalSettings.SecuirtyExternalSessionTrustedOrigins) + + conditionalEntry("cache.metadata.graphql.expireAfterAccess", ordssrvs.Spec.GlobalSettings.CacheMetadataGraphQLExpireAfterAccess) + + conditionalEntry("cache.metadata.jwks.enabled", ordssrvs.Spec.GlobalSettings.CacheMetadataJWKSEnabled) + + conditionalEntry("cache.metadata.jwks.initialCapacity", ordssrvs.Spec.GlobalSettings.CacheMetadataJWKSInitialCapacity) + + conditionalEntry("cache.metadata.jwks.maximumSize", ordssrvs.Spec.GlobalSettings.CacheMetadataJWKSMaximumSize) + + conditionalEntry("cache.metadata.jwks.expireAfterAccess", ordssrvs.Spec.GlobalSettings.CacheMetadataJWKSExpireAfterAccess) + + conditionalEntry("cache.metadata.jwks.expireAfterWrite", ordssrvs.Spec.GlobalSettings.CacheMetadataJWKSExpireAfterWrite) + + conditionalEntry("database.api.management.services.disabled", ordssrvs.Spec.GlobalSettings.DatabaseAPIManagementServicesDisabled) + + conditionalEntry("db.invalidPoolTimeout", ordssrvs.Spec.GlobalSettings.DBInvalidPoolTimeout) + + conditionalEntry("feature.graphql.max.nesting.depth", ordssrvs.Spec.GlobalSettings.FeatureGraphQLMaxNestingDepth) + + conditionalEntry("request.traceHeaderName", ordssrvs.Spec.GlobalSettings.RequestTraceHeaderName) + + conditionalEntry("security.credentials.attempts", ordssrvs.Spec.GlobalSettings.SecurityCredentialsAttempts) + + conditionalEntry("security.credentials.lock.time", ordssrvs.Spec.GlobalSettings.SecurityCredentialsLockTime) + + conditionalEntry("standalone.context.path", ordssrvs.Spec.GlobalSettings.StandaloneContextPath) + + conditionalEntry("standalone.http.port", ordssrvs.Spec.GlobalSettings.StandaloneHTTPPort) + + conditionalEntry("standalone.https.host", ordssrvs.Spec.GlobalSettings.StandaloneHTTPSHost) + + conditionalEntry("standalone.https.port", ordssrvs.Spec.GlobalSettings.StandaloneHTTPSPort) + + conditionalEntry("standalone.stop.timeout", ordssrvs.Spec.GlobalSettings.StandaloneStopTimeout) + + conditionalEntry("cache.metadata.timeout", ordssrvs.Spec.GlobalSettings.CacheMetadataTimeout) + + conditionalEntry("cache.metadata.enabled", ordssrvs.Spec.GlobalSettings.CacheMetadataEnabled) + + conditionalEntry("database.api.enabled", ordssrvs.Spec.GlobalSettings.DatabaseAPIEnabled) + + conditionalEntry("instance.api.enabled", ordssrvs.Spec.GlobalSettings.InstanceAPIEnabled) + + conditionalEntry("debug.printDebugToScreen", ordssrvs.Spec.GlobalSettings.DebugPrintDebugToScreen) + + conditionalEntry("error.responseFormat", ordssrvs.Spec.GlobalSettings.ErrorResponseFormat) + + conditionalEntry("icap.port", ordssrvs.Spec.GlobalSettings.ICAPPort) + + conditionalEntry("icap.secure.port", ordssrvs.Spec.GlobalSettings.ICAPSecurePort) + + conditionalEntry("icap.server", ordssrvs.Spec.GlobalSettings.ICAPServer) + + conditionalEntry("log.procedure", ordssrvs.Spec.GlobalSettings.LogProcedure) + + conditionalEntry("mongo.enabled", ordssrvs.Spec.GlobalSettings.MongoEnabled) + + conditionalEntry("mongo.port", ordssrvs.Spec.GlobalSettings.MongoPort) + + conditionalEntry("mongo.idle.timeout", ordssrvs.Spec.GlobalSettings.MongoIdleTimeout) + + conditionalEntry("mongo.op.timeout", ordssrvs.Spec.GlobalSettings.MongoOpTimeout) + + conditionalEntry("security.disableDefaultExclusionList", ordssrvs.Spec.GlobalSettings.SecurityDisableDefaultExclusionList) + + conditionalEntry("security.exclusionList", ordssrvs.Spec.GlobalSettings.SecurityExclusionList) + + conditionalEntry("security.inclusionList", ordssrvs.Spec.GlobalSettings.SecurityInclusionList) + + conditionalEntry("security.maxEntries", ordssrvs.Spec.GlobalSettings.SecurityMaxEntries) + + conditionalEntry("security.verifySSL", ordssrvs.Spec.GlobalSettings.SecurityVerifySSL) + + conditionalEntry("security.httpsHeaderCheck", ordssrvs.Spec.GlobalSettings.SecurityHTTPSHeaderCheck) + + conditionalEntry("security.forceHTTPS", ordssrvs.Spec.GlobalSettings.SecurityForceHTTPS) + + conditionalEntry("externalSessionTrustedOrigins", ordssrvs.Spec.GlobalSettings.SecuirtyExternalSessionTrustedOrigins) + ` ` + ordsSABase + `/config/global/doc_root/` + "\n" + // Dynamic defStandaloneAccessLog + @@ -159,11 +156,11 @@ func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ords *dbapi.Or } default: // PoolConfigMap - poolName := strings.ToLower(ords.Spec.PoolSettings[poolIndex].PoolName) + poolName := strings.ToLower(ordssrvs.Spec.PoolSettings[poolIndex].PoolName) var defDBNetworkPath string - if ords.Spec.PoolSettings[poolIndex].DBWalletSecret != nil { - defDBNetworkPath = ` ` + ordsSABase + `/config/databases/` + poolName + `/network/admin/` + ords.Spec.PoolSettings[poolIndex].DBWalletSecret.WalletName + `` + "\n" + - conditionalEntry("db.wallet.zip.service", strings.ToUpper(ords.Spec.PoolSettings[poolIndex].DBWalletZipService)) + "\n" + if ordssrvs.Spec.PoolSettings[poolIndex].DBWalletSecret != nil { + defDBNetworkPath = ` ` + ordsSABase + `/config/databases/` + poolName + `/network/admin/` + ordssrvs.Spec.PoolSettings[poolIndex].DBWalletSecret.WalletName + `` + "\n" + + conditionalEntry("db.wallet.zip.service", strings.ToUpper(ordssrvs.Spec.PoolSettings[poolIndex].DBWalletZipService)) + "\n" } else { defDBNetworkPath = ` ` + ordsSABase + `/config/databases/` + poolName + `/network/admin/` + "\n" } @@ -171,56 +168,57 @@ func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ords *dbapi.Or "pool.xml": fmt.Sprint(`` + "\n" + `` + "\n" + `` + "\n" + - ` ` + ords.Spec.PoolSettings[poolIndex].DBUsername + `` + "\n" + - conditionalEntry("db.adminUser", ords.Spec.PoolSettings[poolIndex].DBAdminUser) + - conditionalEntry("db.cdb.adminUser", ords.Spec.PoolSettings[poolIndex].DBCDBAdminUser) + - conditionalEntry("apex.security.administrator.roles", ords.Spec.PoolSettings[poolIndex].ApexSecurityAdministratorRoles) + - conditionalEntry("apex.security.user.roles", ords.Spec.PoolSettings[poolIndex].ApexSecurityUserRoles) + - conditionalEntry("db.credentialsSource", ords.Spec.PoolSettings[poolIndex].DBCredentialsSource) + - conditionalEntry("db.poolDestroyTimeout", ords.Spec.PoolSettings[poolIndex].DBPoolDestroyTimeout) + - conditionalEntry("debug.trackResources", ords.Spec.PoolSettings[poolIndex].DebugTrackResources) + - conditionalEntry("feature.openservicebroker.exclude", ords.Spec.PoolSettings[poolIndex].FeatureOpenservicebrokerExclude) + - conditionalEntry("feature.sdw", ords.Spec.PoolSettings[poolIndex].FeatureSDW) + - conditionalEntry("http.cookie.filter", ords.Spec.PoolSettings[poolIndex].HttpCookieFilter) + - conditionalEntry("jdbc.auth.admin.role", ords.Spec.PoolSettings[poolIndex].JDBCAuthAdminRole) + - conditionalEntry("jdbc.cleanup.mode", ords.Spec.PoolSettings[poolIndex].JDBCCleanupMode) + - conditionalEntry("owa.trace.sql", ords.Spec.PoolSettings[poolIndex].OwaTraceSql) + - conditionalEntry("plsql.gateway.mode", ords.Spec.PoolSettings[poolIndex].PlsqlGatewayMode) + - conditionalEntry("security.jwt.profile.enabled", ords.Spec.PoolSettings[poolIndex].SecurityJWTProfileEnabled) + - conditionalEntry("security.jwks.size", ords.Spec.PoolSettings[poolIndex].SecurityJWKSSize) + - conditionalEntry("security.jwks.connection.timeout", ords.Spec.PoolSettings[poolIndex].SecurityJWKSConnectionTimeout) + - conditionalEntry("security.jwks.read.timeout", ords.Spec.PoolSettings[poolIndex].SecurityJWKSReadTimeout) + - conditionalEntry("security.jwks.refresh.interval", ords.Spec.PoolSettings[poolIndex].SecurityJWKSRefreshInterval) + - conditionalEntry("security.jwt.allowed.skew", ords.Spec.PoolSettings[poolIndex].SecurityJWTAllowedSkew) + - conditionalEntry("security.jwt.allowed.age", ords.Spec.PoolSettings[poolIndex].SecurityJWTAllowedAge) + - conditionalEntry("db.connectionType", ords.Spec.PoolSettings[poolIndex].DBConnectionType) + - conditionalEntry("db.customURL", ords.Spec.PoolSettings[poolIndex].DBCustomURL) + - conditionalEntry("db.hostname", ords.Spec.PoolSettings[poolIndex].DBHostname) + - conditionalEntry("db.port", ords.Spec.PoolSettings[poolIndex].DBPort) + - conditionalEntry("db.servicename", ords.Spec.PoolSettings[poolIndex].DBServicename) + - conditionalEntry("db.sid", ords.Spec.PoolSettings[poolIndex].DBSid) + - conditionalEntry("db.tnsAliasName", ords.Spec.PoolSettings[poolIndex].DBTnsAliasName) + - conditionalEntry("jdbc.DriverType", ords.Spec.PoolSettings[poolIndex].JDBCDriverType) + - conditionalEntry("jdbc.InactivityTimeout", ords.Spec.PoolSettings[poolIndex].JDBCInactivityTimeout) + - conditionalEntry("jdbc.InitialLimit", ords.Spec.PoolSettings[poolIndex].JDBCInitialLimit) + - conditionalEntry("jdbc.MaxConnectionReuseCount", ords.Spec.PoolSettings[poolIndex].JDBCMaxConnectionReuseCount) + - conditionalEntry("jdbc.MaxLimit", ords.Spec.PoolSettings[poolIndex].JDBCMaxLimit) + - conditionalEntry("jdbc.auth.enabled", ords.Spec.PoolSettings[poolIndex].JDBCAuthEnabled) + - conditionalEntry("jdbc.MaxStatementsLimit", ords.Spec.PoolSettings[poolIndex].JDBCMaxStatementsLimit) + - conditionalEntry("jdbc.MinLimit", ords.Spec.PoolSettings[poolIndex].JDBCMinLimit) + - conditionalEntry("jdbc.statementTimeout", ords.Spec.PoolSettings[poolIndex].JDBCStatementTimeout) + - conditionalEntry("jdbc.MaxConnectionReuseTime", ords.Spec.PoolSettings[poolIndex].JDBCMaxConnectionReuseTime) + - conditionalEntry("jdbc.SecondsToTrustIdleConnection", ords.Spec.PoolSettings[poolIndex].JDBCSecondsToTrustIdleConnection) + - conditionalEntry("misc.defaultPage", ords.Spec.PoolSettings[poolIndex].MiscDefaultPage) + - conditionalEntry("misc.pagination.maxRows", ords.Spec.PoolSettings[poolIndex].MiscPaginationMaxRows) + - conditionalEntry("procedure.postProcess", ords.Spec.PoolSettings[poolIndex].ProcedurePostProcess) + - conditionalEntry("procedure.preProcess", ords.Spec.PoolSettings[poolIndex].ProcedurePreProcess) + - conditionalEntry("procedure.rest.preHook", ords.Spec.PoolSettings[poolIndex].ProcedureRestPreHook) + - conditionalEntry("security.requestAuthenticationFunction", ords.Spec.PoolSettings[poolIndex].SecurityRequestAuthenticationFunction) + - conditionalEntry("security.requestValidationFunction", ords.Spec.PoolSettings[poolIndex].SecurityRequestValidationFunction) + - conditionalEntry("soda.defaultLimit", ords.Spec.PoolSettings[poolIndex].SODADefaultLimit) + - conditionalEntry("soda.maxLimit", ords.Spec.PoolSettings[poolIndex].SODAMaxLimit) + - conditionalEntry("restEnabledSql.active", ords.Spec.PoolSettings[poolIndex].RestEnabledSqlActive) + + //` ` + ordssrvs.Spec.PoolSettings[poolIndex].DBUsername + `` + "\n" + + conditionalEntry("db.username", ordssrvs.Spec.PoolSettings[poolIndex].DBUsername) + + conditionalEntry("db.adminUser", ordssrvs.Spec.PoolSettings[poolIndex].DBAdminUser) + + conditionalEntry("db.cdb.adminUser", ordssrvs.Spec.PoolSettings[poolIndex].DBCDBAdminUser) + + conditionalEntry("apex.security.administrator.roles", ordssrvs.Spec.PoolSettings[poolIndex].ApexSecurityAdministratorRoles) + + conditionalEntry("apex.security.user.roles", ordssrvs.Spec.PoolSettings[poolIndex].ApexSecurityUserRoles) + + conditionalEntry("db.credentialsSource", ordssrvs.Spec.PoolSettings[poolIndex].DBCredentialsSource) + + conditionalEntry("db.poolDestroyTimeout", ordssrvs.Spec.PoolSettings[poolIndex].DBPoolDestroyTimeout) + + conditionalEntry("debug.trackResources", ordssrvs.Spec.PoolSettings[poolIndex].DebugTrackResources) + + conditionalEntry("feature.openservicebroker.exclude", ordssrvs.Spec.PoolSettings[poolIndex].FeatureOpenservicebrokerExclude) + + conditionalEntry("feature.sdw", ordssrvs.Spec.PoolSettings[poolIndex].FeatureSDW) + + conditionalEntry("http.cookie.filter", ordssrvs.Spec.PoolSettings[poolIndex].HttpCookieFilter) + + conditionalEntry("jdbc.auth.admin.role", ordssrvs.Spec.PoolSettings[poolIndex].JDBCAuthAdminRole) + + conditionalEntry("jdbc.cleanup.mode", ordssrvs.Spec.PoolSettings[poolIndex].JDBCCleanupMode) + + conditionalEntry("owa.trace.sql", ordssrvs.Spec.PoolSettings[poolIndex].OwaTraceSql) + + conditionalEntry("plsql.gateway.mode", ordssrvs.Spec.PoolSettings[poolIndex].PlsqlGatewayMode) + + conditionalEntry("security.jwt.profile.enabled", ordssrvs.Spec.PoolSettings[poolIndex].SecurityJWTProfileEnabled) + + conditionalEntry("security.jwks.size", ordssrvs.Spec.PoolSettings[poolIndex].SecurityJWKSSize) + + conditionalEntry("security.jwks.connection.timeout", ordssrvs.Spec.PoolSettings[poolIndex].SecurityJWKSConnectionTimeout) + + conditionalEntry("security.jwks.read.timeout", ordssrvs.Spec.PoolSettings[poolIndex].SecurityJWKSReadTimeout) + + conditionalEntry("security.jwks.refresh.interval", ordssrvs.Spec.PoolSettings[poolIndex].SecurityJWKSRefreshInterval) + + conditionalEntry("security.jwt.allowed.skew", ordssrvs.Spec.PoolSettings[poolIndex].SecurityJWTAllowedSkew) + + conditionalEntry("security.jwt.allowed.age", ordssrvs.Spec.PoolSettings[poolIndex].SecurityJWTAllowedAge) + + conditionalEntry("db.connectionType", ordssrvs.Spec.PoolSettings[poolIndex].DBConnectionType) + + conditionalEntry("db.customURL", ordssrvs.Spec.PoolSettings[poolIndex].DBCustomURL) + + conditionalEntry("db.hostname", ordssrvs.Spec.PoolSettings[poolIndex].DBHostname) + + conditionalEntry("db.port", ordssrvs.Spec.PoolSettings[poolIndex].DBPort) + + conditionalEntry("db.servicename", ordssrvs.Spec.PoolSettings[poolIndex].DBServicename) + + conditionalEntry("db.sid", ordssrvs.Spec.PoolSettings[poolIndex].DBSid) + + conditionalEntry("db.tnsAliasName", ordssrvs.Spec.PoolSettings[poolIndex].DBTnsAliasName) + + conditionalEntry("jdbc.DriverType", ordssrvs.Spec.PoolSettings[poolIndex].JDBCDriverType) + + conditionalEntry("jdbc.InactivityTimeout", ordssrvs.Spec.PoolSettings[poolIndex].JDBCInactivityTimeout) + + conditionalEntry("jdbc.InitialLimit", ordssrvs.Spec.PoolSettings[poolIndex].JDBCInitialLimit) + + conditionalEntry("jdbc.MaxConnectionReuseCount", ordssrvs.Spec.PoolSettings[poolIndex].JDBCMaxConnectionReuseCount) + + conditionalEntry("jdbc.MaxLimit", ordssrvs.Spec.PoolSettings[poolIndex].JDBCMaxLimit) + + conditionalEntry("jdbc.auth.enabled", ordssrvs.Spec.PoolSettings[poolIndex].JDBCAuthEnabled) + + conditionalEntry("jdbc.MaxStatementsLimit", ordssrvs.Spec.PoolSettings[poolIndex].JDBCMaxStatementsLimit) + + conditionalEntry("jdbc.MinLimit", ordssrvs.Spec.PoolSettings[poolIndex].JDBCMinLimit) + + conditionalEntry("jdbc.statementTimeout", ordssrvs.Spec.PoolSettings[poolIndex].JDBCStatementTimeout) + + conditionalEntry("jdbc.MaxConnectionReuseTime", ordssrvs.Spec.PoolSettings[poolIndex].JDBCMaxConnectionReuseTime) + + conditionalEntry("jdbc.SecondsToTrustIdleConnection", ordssrvs.Spec.PoolSettings[poolIndex].JDBCSecondsToTrustIdleConnection) + + conditionalEntry("misc.defaultPage", ordssrvs.Spec.PoolSettings[poolIndex].MiscDefaultPage) + + conditionalEntry("misc.pagination.maxRows", ordssrvs.Spec.PoolSettings[poolIndex].MiscPaginationMaxRows) + + conditionalEntry("procedure.postProcess", ordssrvs.Spec.PoolSettings[poolIndex].ProcedurePostProcess) + + conditionalEntry("procedure.preProcess", ordssrvs.Spec.PoolSettings[poolIndex].ProcedurePreProcess) + + conditionalEntry("procedure.rest.preHook", ordssrvs.Spec.PoolSettings[poolIndex].ProcedureRestPreHook) + + conditionalEntry("security.requestAuthenticationFunction", ordssrvs.Spec.PoolSettings[poolIndex].SecurityRequestAuthenticationFunction) + + conditionalEntry("security.requestValidationFunction", ordssrvs.Spec.PoolSettings[poolIndex].SecurityRequestValidationFunction) + + conditionalEntry("soda.defaultLimit", ordssrvs.Spec.PoolSettings[poolIndex].SODADefaultLimit) + + conditionalEntry("soda.maxLimit", ordssrvs.Spec.PoolSettings[poolIndex].SODAMaxLimit) + + conditionalEntry("restEnabledSql.active", ordssrvs.Spec.PoolSettings[poolIndex].RestEnabledSqlActive) + defDBNetworkPath + // Disabled (but not forgotten) // conditionalEntry("autoupgrade.api.aulocation", ords.Spec.PoolSettings[poolIndex].AutoupgradeAPIAulocation) + @@ -232,7 +230,7 @@ func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ords *dbapi.Or } } - objectMeta := objectMetaDefine(ords, configMapName) + objectMeta := objectMetaDefine(ordssrvs, configMapName) def := &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{ Kind: "ConfigMap", @@ -243,7 +241,7 @@ func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ords *dbapi.Or } // Set the ownerRef - if err := ctrl.SetControllerReference(ords, def, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(ordssrvs, def, r.Scheme); err != nil { return nil } return def diff --git a/docs/ordsservices/README.md b/docs/ordsservices/README.md index d012d4ef..9745e0ea 100644 --- a/docs/ordsservices/README.md +++ b/docs/ordsservices/README.md @@ -18,6 +18,7 @@ The custom RestDataServices resource supports the following configurations as a * Multiple OrdsSrvs resources, each with one database pool * Multiple OrdsSrvs resources, each with multiple database pools* * ORDS and APEX database schemas [automatic installation/upgrade](./autoupgrade.md) +* Deploying ORDS with Central Configuration Server *See [Limitations](#limitations) @@ -34,6 +35,24 @@ OrdsSrvs controller supports the majority of ORDS configuration settings as per Before installing the OrdsSrvs controller, ensure that the Oracle Database Operator (OraOperator) is installed in your Kubernetes environment. Please follow the detailed installation steps provided in the [README](https://github.com/oracle/oracle-database-operator/blob/main/README.md) to complete this process. The OraOperator must be properly configured and running, as OrdsSrvs depends on its services for functionality. +## Common configuration examples + +A few common configuration examples can be used to quickly familiarise yourself with the OrdsSrvs Custom Resource Definition. +The "Conclusion" section of each example highlights specific settings to enable functionality that maybe of interest. + +* [Pre-existing Database](./examples/existing_db.md) +* [Containerised Single Instance Database (SIDB)](./examples/sidb_container.md) +* [Multidatabase using a TNS Names file](./examples/multi_pool.md) +* [Autonomous Database using the OraOperator](./examples/adb_oraoper.md) *See [Limitations](#limitations) +* [Autonomous Database without the OraOperator](./examples/adb.md) +* [Oracle API for MongoDB Support](./examples/mongo_api.md) +* [ORDS and APEX database schemas automatic installation/upgrade](./autoupgrade.md) +* [External tnsnames.ora and Database credentials in an Oracle Wallet](./examples/resources.md) +* [Deploying ORDS with Central Configuration Server](./examples/central_configuration.md) + +Running through all examples in the same Kubernetes cluster illustrates the ability to run multiple ORDS instances with a variety of different configurations. + + ### Namespace Namespace Scoped Deployment For a dedicated namespace deployment of the OrdsSrvs controller, refer to the "Namespace Scoped Deployment" section in the OraOperator [README](https://github.com/oracle/oracle-database-operator/blob/main/README.md#2-namespace-scoped-deployment). @@ -99,23 +118,25 @@ spec: ``` -## Common configuration examples +## Change Log -A few common configuration examples can be used to quickly familiarise yourself with the OrdsSrvs Custom Resource Definition. -The "Conclusion" section of each example highlights specific settings to enable functionality that maybe of interest. +### Development -* [Pre-existing Database](./examples/existing_db.md) -* [Containerised Single Instance Database (SIDB)](./examples/sidb_container.md) -* [Multidatabase using a TNS Names file](./examples/multi_pool.md) -* [Autonomous Database using the OraOperator](./examples/adb_oraoper.md) *See [Limitations](#limitations) -* [Autonomous Database without the OraOperator](./examples/adb.md) -* [Oracle API for MongoDB Support](./examples/mongo_api.md) -* [ORDS and APEX database schemas automatic installation/upgrade](./autoupgrade.md) -Running through all examples in the same Kubernetes cluster illustrates the ability to run multiple ORDS instances with a variety of different configurations. +### Version 2.1 + +* **Native Kubernetes Secret** +`encPrivKey` attribute is now optional; passwords can be stored in native K8s Secrets without additional encryption. +* **TNSAdminSecret** +adding a dedicated documentation page. see [resources](./docs/ordsservices/examples/resources.md) +* **poolWalletSecret** +New attribute `poolWalletSecret` lets you reference an external Oracle Wallet that contains the database credentials, avoiding storage of the password in a Kubernetes Secret. +see [resources](./docs/ordsservices/examples/resources.md) +* **central.config.url** +Deploying ORDS with Central Configuration Server. +New attribute `central.config.url` (e.g. https://central-config.example.com:8585/central/v1/config) -## Change Log ### Version 2.0 diff --git a/docs/ordsservices/api.md b/docs/ordsservices/api.md index 4268e98c..c84ff7d8 100644 --- a/docs/ordsservices/api.md +++ b/docs/ordsservices/api.md @@ -162,7 +162,17 @@ Deployment or StatefulSet
secretName: string  passwordKey: string Define the private key to decrypt passwords
-true
+false
+ + + +central.config.url
+
+string
+ +Central Configuration Server url (e.g. https://central-config.example.com:8585/central/v1/config) + +false
diff --git a/docs/ordsservices/examples/central_configuration.md b/docs/ordsservices/examples/central_configuration.md new file mode 100644 index 00000000..d4af19e5 --- /dev/null +++ b/docs/ordsservices/examples/central_configuration.md @@ -0,0 +1,340 @@ +# OrdsSrvs Controller: Central Configuration via central.config.url + +This feature introduces support for configuring ORDS instances managed by the OrdsSrvs controller using a central configuration manager. By setting the `central.config.url` attribute, OrdsSrvs retrieves global and pool-specific settings from a central endpoint that implements the ORDS Central Config Manager OpenAPI. + +This document shows: +- A minimal OrdsSrvs example that uses `central.config.url` +- A demo central config manager implemented with Apache HTTPD in Kubernetes +- How to validate the setup and make REST calls against multiple pools + +See ORDS docs: [Configuring additional databases][ords-config-addl-dbs] (tested with 25.3): +https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/25.3/ordig/configuring-additional-databases.html#GUID-EEDA7256-7EDE-467B-B71D-6C7C184D982E + +>Note: This example is for demo/testing only. Do not use plaintext passwords or HTTP in production. + +## Overview + +- Central configuration endpoint: + - Global config: `GET /central/v1/config` + - Pool config: `GET /central/v1/config/pool/{poolName}` +- In this example, the pool is resolved from the URL path using `security.externalMappingPathPrefix = true`. +- Example pools: `pool-a` and `pool-b` +- ORDS base path: `/ords` +- Example REST access: `/ords/{poolName}/...` + +## Prerequisites + +See ORDSSRVS prerequisites: [ORDSSRVS prerequisites](../README.md#prerequisites) + +In addition to the above, you’ll need: +- A reachable Oracle database for the pools +- Target Kubernetes namespace (replace NAMESPACE) + +Security reminder: +>- Use HTTPS/TLS for the central config service and for ORDS in production. +>- Store credentials in Kubernetes Secrets (not ConfigMaps) and avoid plaintext passwords. +>- Restrict network access via NetworkPolicies. + +## Central Config Manager files + +Create these four files locally. We will package them into a ConfigMap. + +- **central-config-httpd.conf** httpd config +- **central-config-global.json** ORDS global configuration +- **central-config-pool-a.json** pool-a configuration +- **central-config-pool-b.json** pool-b configuration + +central-config-httpd.conf: +``` +ServerName localhost +ServerRoot "/usr/local/apache2" +Listen 80 +LoadModule mpm_event_module modules/mod_mpm_event.so +LoadModule authz_core_module modules/mod_authz_core.so +LoadModule dir_module modules/mod_dir.so +LoadModule mime_module modules/mod_mime.so +LoadModule log_config_module modules/mod_log_config.so +LoadModule rewrite_module modules/mod_rewrite.so +LoadModule unixd_module modules/mod_unixd.so +LoadModule alias_module modules/mod_alias.so +User www-data +Group www-data +PidFile /usr/local/apache2/logs/httpd.pid +ErrorLog /proc/self/fd/2 +LogLevel warn +CustomLog /proc/self/fd/1 common +DocumentRoot "/usr/local/apache2/htdocs" + + AllowOverride None + Require all granted + +AddType application/json .json +FileETag MTime Size +RewriteEngine On +RewriteRule ^/health$ /ok.txt [PT,L] +#RewriteCond %{REQUEST_URI} ^/central/ +#RewriteCond %{HTTP:Request-Id} ="" +#RewriteRule ^ - [R=400,L] +RewriteRule ^/central/v1/config$ /global.json [PT,L] +#RewriteRule ^/central/v1/config/pool/localhost /pools/default.json [PT,L] +RewriteRule ^/central/v1/config/pool/([^/]+)$ /pools/$1.json [PT,L] +DirectoryIndex disabled +``` + +central-config-global.json: +``` +{ + "settings": { + "security.externalMappingPathPrefix": true, + "restEnabledSql.active": true, + "feature.sdw": true, + "debug.debugger": "debug", + "standalone.https.port": "8443", + "standalone.http.port": "8080", + "security.forceHTTPS": false, + "standalone.context.path": "/ords" + }, + "links": [ + { + "rel": "collection", + "href": "http://central-config-svc/central/v1/config/" + }, + { + "rel": "self", + "href": "http://central-config-svc/central/v1/config/" + }, + { + "rel": "search", + "href": "http://central-config-svc/central/v1/config/pool/{host}", + "templated": true + } + ] +} +``` + +central-config-pool-a.json: +``` +{ + "database": { + "pool": { + "name": "pool-a", + "settings": { + "db.connectionType": "customurl", + "db.customURL": "jdbc:oracle:thin:@sidb:1521/FREEPDB1", + "db.username": "ORDS_PUBLIC_USER", + "db.password": "" + } + } + } +} +``` + +central-config-pool-b.json: +``` +{ + "database": { + "pool": { + "name": "pool-b", + "settings": { + "db.connectionType": "customurl", + "db.customURL": "jdbc:oracle:thin:@sidb:1521/FREEPDB1", + "db.username": "ORDS_PUBLIC_USER", + "db.password": "" + } + } + } +} +``` + +>Important: Passwords here are for testing only. + +## Create the ConfigMap with the central config content + +``` +kubectl -n NAMESPACE create configmap central-config \ + --from-file=central-config-httpd.conf \ + --from-file=central-config-global.json \ + --from-file=central-config-pool-a.json \ + --from-file=central-config-pool-b.json +``` + +## Deploy the demo central config server (Apache HTTPD) and Service + +central-config-server.template: +``` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: central-config-server + namespace: NAMESPACE +spec: + replicas: 2 + selector: + matchLabels: + app: central-config-server + template: + metadata: + labels: + app: central-config-server + spec: + containers: + - name: httpd + image: docker.io/library/httpd:latest + ports: + - containerPort: 80 + volumeMounts: + - name: httpd-conf + mountPath: /usr/local/apache2/conf/httpd.conf + subPath: httpd.conf + readOnly: true + - name: config + mountPath: /usr/local/apache2/htdocs + readOnly: true + volumes: + - name: httpd-conf + configMap: + name: central-config + items: + - key: central-config-httpd.conf + path: httpd.conf + - name: config + configMap: + name: central-config + items: + - key: central-config-global.json + path: global.json + - key: central-config-pool-a.json + path: pools/pool-a.json + - key: central-config-pool-b.json + path: pools/pool-b.json +--- +apiVersion: v1 +kind: Service +metadata: + name: central-config-svc + namespace: NAMESPACE +spec: + type: ClusterIP + selector: + app: central-config-server + ports: + - port: 80 + targetPort: 80 +``` + +Apply: +``` +kubectl apply -f central-config-server.template +``` + +## Validate the central config endpoints + +from the cluster network: +``` + curl http://central-config-svc/central/v1/config + curl http://central-config-svc/central/v1/config/pool/pool-a + curl http://central-config-svc/central/v1/config/pool/pool-b +``` + +You should receive the JSON documents defined above. + +## Configure OrdsSrvs to use central.config.url + +OrdsSrvs manifest: +``` +apiVersion: database.oracle.com/v4 +kind: OrdsSrvs +metadata: + name: ordssrvs-cc + namespace: NAMESPACE +spec: + image: container-registry.oracle.com/database/ords:latest + central.config.url: http://central-config-svc/central/v1/config + serviceAccountName: ordssrvs-sa +``` + +Apply: +``` +kubectl apply -f ordssrvs-central-config.yaml +``` + +The controller will: +- Fetch the global config from `/central/v1/config` +- Resolve the pool by URL path (because `security.externalMappingPathPrefix` is true) +- Fetch pool configs from `/central/v1/config/pool/{poolName}` as requests arrive + +## Prepare a test schema and object in the database + +Run the following in your database (adjust schema if needed). Example uses schema `ORDSSRVSTESTCASE`. + +``` +CREATE TABLE TESTCASE ( STATUS VARCHAR2 (100)); +TRUNCATE TABLE TESTCASE; +INSERT INTO TESTCASE VALUES ('ORDSSRVS_TESTCASE_CHECK'); + +BEGIN + ORDS.enable_schema( + p_enabled => TRUE, + p_schema => 'ORDSSRVSTESTCASE', + p_url_mapping_type => 'BASE_PATH', + p_url_mapping_pattern => 'ordssrvs_testcase', + p_auto_rest_auth => FALSE + ); +END; +/ + +BEGIN + ORDS.ENABLE_OBJECT( + p_enabled => TRUE, + p_schema => 'ORDSSRVSTESTCASE', + p_object => 'TESTCASE', + p_object_type => 'TABLE', + p_object_alias => 'testcase_table' + ); +END; +/ +``` + +## Test REST calls through ORDS with path-based pool resolution + +Assuming ORDS is listening on 8443 and using the `/ords` context path: + +``` +curl -ik https://ordssrvs-cc:8443/ords/pool-a/ordssrvs_testcase/testcase_table/ -H "Host: localhost" +curl -ik https://ordssrvs-cc:8443/ords/pool-b/ordssrvs_testcase/testcase_table/ -H "Host: localhost" +``` + +- The pool is resolved from the path segment after `/ords/` (pool-a or pool-b). +- There is no server-name-to-pool mapping in this example. + +## Troubleshooting + +- OrdsSrvs not picking up central config: + - Check logs of the OrdsSrvs pods for fetch errors + - Confirm `central.config.url` is reachable via in-cluster DNS + - Validate central config endpoints return HTTP 200 and JSON + +- Pool resolution issues: + - Ensure `security.externalMappingPathPrefix = true` in the global config + - Confirm the path is `/ords/{poolName}/...` + +- Database connectivity: + - Verify `db.customURL`, `db.username`, and credentials for each pool + - Use Secrets for credentials in production + - Check network access from ORDS pods to the database + +- Security: + - Use TLS for the central config service (`https`) and for ORDS + - Align with Oracle internal security/compliance guidelines when deploying third-party images (e.g., httpd) + +## Notes and Best Practices + +- Replace `NAMESPACE`, and `` with your values. +- For production: + - Replace HTTP with HTTPS and configure trusted certificates + - Move credentials to Secrets or wallet-based authentication + - Add health and readiness probes to both central-config and ORDS + - Apply NetworkPolicies to limit ingress/egress + - Implement caching/failover strategies for central config availability + +This example demonstrates how OrdsSrvs can centrally source both global and pool configurations, enabling consistent, scalable ORDS deployments with path-based pool selection. \ No newline at end of file diff --git a/docs/ordsservices/examples/resources.md b/docs/ordsservices/examples/resources.md new file mode 100644 index 00000000..47492ee2 --- /dev/null +++ b/docs/ordsservices/examples/resources.md @@ -0,0 +1,62 @@ +# OrdsSrvs Example: external tnsnames.ora and Oracle Wallet + +This example demonstrates how to: +- Provide an external tnsnames.ora to the OrdsSrvs controller via a Kubernetes Secret. +- Supply database credentials using an Oracle Wallet stored in a Kubernetes Secret. + +>Note: The attributes tnsAdminSecret and poolWalletSecret are independent and can be used separately. + +## Create a tnsnames.ora Secret +Prepare a folder named "resources" containing a tnsnames.ora file, then create a Kubernetes Secret from it. + +```bash +mkdir -p resources +cat > resources/tnsnames.ora <)(PORT = )) + (CONNECT_DATA = + (SERVICE_NAME = ) + ) + ) +EOF + +kubectl create secret -n generic myresources-tns-admin --from-file=./resources/tnsnames.ora +``` + +Replace \, \, \, and \ with your values. + +## Prepare Credentials in an Oracle Wallet + +Use an existing Oracle Wallet (created with the orapki utility) that contains the credentials (entries for db.admin and db.adminUser.secret), or create a new wallet with orapki. In this example, we reuse a wallet from a previous OrdsSrvs installation. + +```bash +mkdir -p resources +cp ./saved/cwallet.sso ./resources/cwallet.sso + +kubectl create secret -n generic myresources-pool-wallet --from-file=./resources/cwallet.sso +``` + +## Create the OrdsSrvs Resource + +Below is a snippet (not a complete manifest) showing how to reference the external tnsnames.ora and the Oracle Wallet in a pool of the OrdsSrvs resource. + +```yaml +... +- poolName: example-pool + db.connectionType: tns + db.tnsAliasName: TESTCASE + tnsAdminSecret: + secretName: myresources-tns-admin + poolWalletSecret: + secretName: myresources-pool-wallet + db.username: ORDS_PUBLIC_USER + db.adminUser: SYS +... +``` + +## Notes + +- tnsAdminSecret and poolWalletSecret have no dependencies on each other and can be used independently. +- Ensure the db.tnsAliasName matches the alias present in tnsnames.ora (TESTCASE in this example). +- Handle Kubernetes Secrets and wallet files in accordance with your organization’s security and compliance policies. \ No newline at end of file diff --git a/ordssrvs/RSADecryptOAEP.java b/ordssrvs/RSADecryptOAEP.java new file mode 100644 index 00000000..624155c1 --- /dev/null +++ b/ordssrvs/RSADecryptOAEP.java @@ -0,0 +1,66 @@ +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.Path; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; +import javax.crypto.Cipher; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; +import java.security.NoSuchAlgorithmException; +import java.security.InvalidKeyException; +import java.security.spec.InvalidKeySpecException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import java.security.InvalidAlgorithmParameterException; +import java.security.spec.MGF1ParameterSpec; +import java.util.Base64; + +public class RSADecryptOAEP { + + public static void main(String[] args) throws Exception { + + if (args.length != 2) { + return; + } + + String privateKeyPath = "/opt/oracle/sa/encryptionPrivateKey"; + String privateKeyName = args[0]; + String encodedEncryptedPassword = args[1]; + + byte[] encodedEncryptedDataBytes = encodedEncryptedPassword.getBytes(); + byte[] encryptedData = Base64.getMimeDecoder().decode(encodedEncryptedDataBytes); + + Path privateKeyDir = Paths.get(privateKeyPath).toAbsolutePath(); + Path requestedPath = privateKeyDir.resolve(privateKeyName).normalize(); + String enc64 = new String(Files.readAllBytes(requestedPath)) + .replace("-----"+"BEGIN PRIVATE KEY"+"-----", "") + .replace("-----"+"END PRIVATE KEY"+"-----", "") + .replaceAll("\\s", ""); + + byte[] privateKeyBytes = Base64.getDecoder().decode(enc64); + + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); + PrivateKey privateKey = keyFactory.generatePrivate(keySpec); + + String oaepPadding = "OAEPWithSHA-256AndMGF1Padding"; + Cipher cipher = Cipher.getInstance("RSA/ECB/" + oaepPadding); + + OAEPParameterSpec oaepParams = new OAEPParameterSpec( + "SHA-256", + "MGF1", + new MGF1ParameterSpec("SHA-256"), + new PSource.PSpecified(new byte[0]) + ); + + cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParams); + + byte[] decryptedData = cipher.doFinal(encryptedData); + + System.out.printf(new String(decryptedData)); + + } +} diff --git a/ordssrvs/ords_init.sh b/ordssrvs/ords_init.sh new file mode 100644 index 00000000..bd4cead1 --- /dev/null +++ b/ordssrvs/ords_init.sh @@ -0,0 +1,928 @@ +#!/bin/bash +## Copyright (c) 2006, 2024, Oracle and/or its affiliates. +## +## The Universal Permissive License (UPL), Version 1.0 +## +## Subject to the condition set forth below, permission is hereby granted to any +## person obtaining a copy of this software, associated documentation and/or data +## (collectively the "Software"), free of charge and under any and all copyright +## rights in the Software, and any and all patent rights owned or freely +## licensable by each licensor hereunder covering either (i) the unmodified +## Software as contributed to or provided by such licensor, or (ii) the Larger +## Works (as defined below), to deal in both +## +## (a) the Software, and +## (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +## one is included with the Software (each a "Larger Work" to which the Software +## is contributed by such licensors), +## +## without restriction, including without limitation the rights to copy, create +## derivative works of, display, perform, and distribute the Software and make, +## use, sell, offer for sale, import, export, have made, and have sold the +## Software and the Larger Work(s), and to sublicense the foregoing rights on +## either these or other terms. +## +## This license is subject to the following condition: +## The above copyright notice and either this complete permission notice or at +## a minimum a reference to the UPL must be included in all copies or +## substantial portions of the Software. +## +## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +## SOFTWARE. + +# not used, unset to avoid warning message +unset JAVA_TOOL_OPTIONS + +dump_stack(){ +_log_date=$(date "+%y:%m:%d %H:%M:%S") + local frame=0 + local line_no + local function_name + local file_name + echo -e "BACKTRACE [${_log_date}]\n" + echo -e "filename:line\tfunction " + echo -e "------------- --------" + while caller $frame ;do ((frame++)) ;done | \ + while read -r line_no function_name file_name;\ + do echo -e "$file_name:$line_no\t$function_name" ;done >&2 +} + +sep(){ + echo "### =====================================================================================" +} + +sub(){ + echo "### $(date +'%Y-%m-%d %H:%M:%S') ### $1" +} + +#------------------------------------------------------------------------------ +function global_parameters(){ + + APEX_INSTALL=/opt/oracle/apex + # backward compatibility for ORDS images prior to 24.1.x (included) + # APEX_HOME is used only here and it is set on images <= 24.1.x + if [[ -n ${APEX_HOME} ]]; then + APEX_INSTALL=${APEX_HOME}/${APEX_VER} + echo "WARNING: APEX_HOME detected, APEX_HOME:${APEX_HOME}; ORDS image may be older than 24.2" + fi + APEXINS=${APEX_INSTALL}/apexins.sql + APEX_IMAGES=${APEX_INSTALL}/images + APEX_VERSION_TXT=${APEX_IMAGES}/apex_version.txt + + sub "global parameters" + echo "external_apex : ${external_apex:?}" + echo "download_apex : ${download_apex:?}" + + if [[ ${download_apex} == "true" ]] + then + echo "download_url_apex : ${download_url_apex:?}" + fi + + if [[ ( ${external_apex} == "true" ) || ( ${download_apex} == "true" ) ]] + then + echo "APEX_INSTALL : $APEX_INSTALL" + echo "APEX_IMAGES : $APEX_IMAGES" + fi + + if [[ -n ${central_config_url} ]] + then + echo "central_config_url : ${central_config_url}" + fi + + # check for password encryption + if [[ -n "${ENC_PRV_KEY}" ]]; then + ENC_PRV_KEY_PATH="/opt/oracle/sa/encryptionPrivateKey" + ENC_PRV_KEY_FILE="${ENC_PRV_KEY_PATH}/${ENC_PRV_KEY}" + echo "encryption key name : ${ENC_PRV_KEY}" + [[ -f "${ENC_PRV_KEY_FILE}" ]] || echo "ERROR: encryption key not found" + javac /opt/oracle/sa/scripts/RSADecryptOAEP.java -d /opt/oracle/ords/scripts/ + fi +} + +#------------------------------------------------------------------------------ +prepare_pool_connect_string() { + local -n _x_conn_string="${1}" + local -r _conn_type="${2}" + local -r _user="${3%%/ *}" + local -r _pwd="${4}" + + sub "connect string" + if [[ -n "${_user}" ]] + then + echo "username : ${_user}" + else + echo "username : / (SEPS)" + fi + + if [[ $_conn_type == "customurl" ]]; then + local -r _conn=$($ords_cfg_cmd get db.customURL | tail -1) + elif [[ $_conn_type == "tns" ]]; then + local -r _tns_service=$($ords_cfg_cmd get db.tnsAliasName | tail -1) + local -r _conn=${_tns_service} + elif [[ $_conn_type == "basic" ]]; then + local -r _host=$($ords_cfg_cmd get db.hostname | tail -1) + local -r _port=$($ords_cfg_cmd get db.port | tail -1) + local -r _service=$($ords_cfg_cmd get db.servicename | tail -1) + local -r _sid=$($ords_cfg_cmd get db.sid | tail -1) + + if [[ -n ${_host} ]] && [[ -n ${_port} ]]; then + if [[ -n ${_service} ]] || [[ -n ${_sid} ]]; then + local -r _conn=${_host}:${_port}/${_service:-$_sid} + fi + fi + elif [[ $_conn_type == "dbWalletZip" ]]; then + local -r _zip_wallet_service=$($ords_cfg_cmd get db.wallet.zip.service | tail -1) + local -r _conn="${_zip_wallet_service}" + else + echo "Empty or unknown connectionType" + return 1 + fi + + if [[ ( -n ${_conn} ) ]]; then + echo "connect string : ${_conn}" + _x_conn_string="${_user}/${_pwd}@${_conn}" + if [[ ${_user} == "SYS" ]]; then + _x_conn_string="${_x_conn_string=} AS SYSDBA" + fi + fi +} + +#------------------------------------------------------------------------------ +function setup_sql_environment(){ + + sub "Configuring sql environment" + + ## Get TNS_ADMIN location + local -r _tns_admin=$($ords_cfg_cmd get db.tnsDirectory 2>&1| tail -1) + if [[ ! $_tns_admin =~ "Cannot get setting" ]]; then + echo "Setting: TNS_ADMIN=${_tns_admin}" + export TNS_ADMIN=${_tns_admin} + [[ -d ${TNS_ADMIN} ]] && ls -l ${TNS_ADMIN} + fi + + ## Get ADB Wallet + # echo "Checking db.wallet.zip.path" + local -r _wallet_zip_path=$($ords_cfg_cmd get db.wallet.zip.path 2>&1| tail -1) + if [[ ! $_wallet_zip_path =~ "Cannot get setting" ]]; then + config[cloudconfig]="${_wallet_zip_path}" + echo "db.wallet.zip.path is set, using : set cloudconfig ${config[cloudconfig]}" + fi + + return 0 + +} + +function run_sql { + local -r _sql="${1}" + local -n _output="${2}" + local -r _heading="${3-off}" + local -i _rc=0 + + if [[ -z ${_sql} ]]; then + dump_stack + echo "FATAL: missing SQL calling run\_sql" && exit 1 + fi + + #echo "DEBUG Running SQL" + #echo "DEBUG set cloudconfig ${config[cloudconfig]}" + #echo "DEBUG connect ${config[connect]}" + + # NOTE to maintainer; the heredoc must be TAB indented + _output=$(sql -S -nohistory -noupdates /nolog <<-EOSQL + WHENEVER SQLERROR EXIT 1 + WHENEVER OSERROR EXIT 1 + set cloudconfig ${config[cloudconfig]} + connect ${config[connect]} + set serveroutput on echo off pause off feedback off + set heading ${_heading} wrap off linesize 1000 pagesize 0 + SET TERMOUT OFF VERIFY OFF + ${_sql} + exit; + EOSQL + ) + _rc=$? + + if (( _rc > 0 )); then + dump_stack + echo "SQLERROR: ${_output}" + fi + + return $_rc +} + +#------------------------------------------------------------------------------ +function check_adb() { + local -n _is_adb=$1 + + sub "ADB check" + + local -r _adb_chk_sql=" + DECLARE + invalid_column exception; + pragma exception_init (invalid_column,-00904); + adb_check integer; + BEGIN + EXECUTE IMMEDIATE q'[SELECT COUNT(*) FROM ( + SELECT JSON_VALUE(cloud_identity, '\$.DATABASE_OCID') AS database_ocid + FROM v\$pdbs) t + WHERE t.database_ocid like '%AUTONOMOUS%']' INTO adb_check; + DBMS_OUTPUT.PUT_LINE(adb_check); + EXCEPTION WHEN invalid_column THEN + DBMS_OUTPUT.PUT_LINE('0'); + END; + /" + echo "Checking if Database is an ADB" + run_sql "${_adb_chk_sql}" "_adb_check" + _rc=$? + + if (( _rc == 0 )); then + _adb_check=${_adb_check//[[:space:]]/} + if (( _adb_check == 1 )); then + _is_adb=${_adb_check//[[:space:]]/} + echo "ADB : yes" + else + echo "ADB : no" + fi + else + echo "ADB check failed" + fi + + return ${_rc} +} + +function create_adb_user() { + local -r _pool_name="${1}" + + local _config_user + _config_user=$($ords_cfg_cmd get db.username | tail -1) + + if [[ -z "${_config_user}" ]] || [[ "${_config_user}" == "ORDS_PUBLIC_USER" ]]; then + echo "FATAL: You must specify a db.username <> ORDS_PUBLIC_USER in pool \"${_pool_name}\"" + dump_stack + return 1 + fi + + local -r _adb_user_sql=" + DECLARE + l_user VARCHAR2(255); + l_cdn VARCHAR2(255); + BEGIN + BEGIN + SELECT USERNAME INTO l_user FROM DBA_USERS WHERE USERNAME='${_config_user}'; + EXECUTE IMMEDIATE 'ALTER USER \"${_config_user}\" PROFILE ORA_APP_PROFILE'; + EXECUTE IMMEDIATE 'ALTER USER \"${_config_user}\" IDENTIFIED BY \"${config[dbpassword]}\"'; + DBMS_OUTPUT.PUT_LINE('${_config_user} Exists - Password reset'); + EXCEPTION + WHEN NO_DATA_FOUND THEN + EXECUTE IMMEDIATE 'CREATE USER \"${_config_user}\" IDENTIFIED BY \"${config[dbpassword]}\" PROFILE ORA_APP_PROFILE'; + DBMS_OUTPUT.PUT_LINE('${_config_user} Created'); + END; + EXECUTE IMMEDIATE 'GRANT CONNECT TO \"${_config_user}\"'; + BEGIN + SELECT USERNAME INTO l_user FROM DBA_USERS WHERE USERNAME='ORDS_PLSQL_GATEWAY_OPER'; + EXECUTE IMMEDIATE 'ALTER USER \"ORDS_PLSQL_GATEWAY_OPER\" PROFILE DEFAULT'; + EXECUTE IMMEDIATE 'ALTER USER \"ORDS_PLSQL_GATEWAY_OPER\" NO AUTHENTICATION'; + DBMS_OUTPUT.PUT_LINE('ORDS_PLSQL_GATEWAY_OPER Exists'); + EXCEPTION + WHEN NO_DATA_FOUND THEN + EXECUTE IMMEDIATE 'CREATE USER \"ORDS_PLSQL_GATEWAY_OPER\" NO AUTHENTICATION PROFILE DEFAULT'; + DBMS_OUTPUT.PUT_LINE('ORDS_PLSQL_GATEWAY_OPER Created'); + END; + EXECUTE IMMEDIATE 'GRANT CONNECT TO \"ORDS_PLSQL_GATEWAY_OPER\"'; + EXECUTE IMMEDIATE 'ALTER USER \"ORDS_PLSQL_GATEWAY_OPER\" GRANT CONNECT THROUGH \"${_config_user}\"'; + ORDS_ADMIN.PROVISION_RUNTIME_ROLE ( + p_user => '${_config_user}' + ,p_proxy_enabled_schemas => TRUE + ); + ORDS_ADMIN.CONFIG_PLSQL_GATEWAY ( + p_runtime_user => '${_config_user}' + ,p_plsql_gateway_user => 'ORDS_PLSQL_GATEWAY_OPER' + ); + -- TODO: Only do this if ADB APEX Version <> this ORDS Version + BEGIN + SELECT images_version INTO L_CDN + FROM APEX_PATCHES + where is_bundle_patch = 'Yes' + order by patch_version desc + fetch first 1 rows only; + EXCEPTION WHEN NO_DATA_FOUND THEN + select version_no INTO L_CDN + from APEX_RELEASE; + END; + apex_instance_admin.set_parameter( + p_parameter => 'IMAGE_PREFIX', + p_value => 'https://static.oracle.com/cdn/apex/'||L_CDN||'/' + ); + END; + /" + + local _adb_user_sql_output + run_sql "${_adb_user_sql}" "_adb_user_sql_output" + _rc=$? + + echo "Installation Output: ${_adb_user_sql_output}" + return ${_rc} +} + +#------------------------------------------------------------------------------ +function apex_compare_versions() { + local _db_ver=$1 + local _im_ver=$2 + + echo "database APEX version : $_db_ver" + echo "install APEX version : $_im_ver" + + IFS='.' read -r -a _db_ver_array <<< "$_db_ver" + IFS='.' read -r -a _im_ver_array <<< "$_im_ver" + + # Compare each component + local i + for i in "${!_db_ver_array[@]}"; do + if [[ "${_db_ver_array[$i]}" -lt "${_im_ver_array[$i]}" ]]; then + # _db_ver < _im_ver (upgrade) + return 0 + elif [[ "${_db_ver_array[$i]}" -gt "${_im_ver_array[$i]}" ]]; then + # _db_ver < _im_ver (do nothing) + return 1 + fi + done + # _db_ver == __im_ver (do nothing) + return 1 +} + +#------------------------------------------------------------------------------ +ords_client_version(){ + sub "ORDS client version" + ORDSVERSION=$(ords --config "${ORDS_CONFIG}" --version 2>&1 | tail -1) + echo "ords_client_version : \"$ORDSVERSION\"" +} + +#------------------------------------------------------------------------------ +set_ords_secret() { + local -r _pool_name="${1}" + local -r _config_key="${2}" + local -r _config_val="${3}" + local -i _rc=0 + + if [[ -n "${_config_val}" ]]; then + echo "Setting ${_config_key} in pool \"${_pool_name}\"" + ords --config "$ORDS_CONFIG" config --db-pool "${_pool_name}" secret --password-stdin "${_config_key}" 2>&1 <<< "${_config_val}"|tail -1 + _rc=$? + else + echo "${_config_key} in pool \"${_pool_name}\" is not defined" + _rc=0 + fi + + return ${_rc} +} + +#------------------------------------------------------------------------------ +setup_credentials(){ + + + sub "Setup Credentials" + + # reading users from env + for key in dbusername dbadminuser dbcdbadminuser dbconnectiontype; do + var_key="${pool_name_underscore}_${key}" + var_val="${!var_key}" + if [[ (-n "${var_val}") ]]; then + config[${key}]="${var_val}" + fi + done + + # reading passwords from env and eventually decrypting + for key in dbpassword dbadminuserpassword dbcdbadminuserpassword; do + var_key="${pool_name_underscore}_${key}" + echo "Obtaining value from initContainer variable: ${var_key}" + var_val="${!var_key}" + if [[ (-n "${var_val}") && (-n "${ENC_PRV_KEY}") && (-f "${ENC_PRV_KEY_FILE}") ]]; then + echo "Decrypting ${var_key}" + cd /opt/oracle/ords/scripts || return + var_val_enc=${var_val} + var_val=$(java RSADecryptOAEP "${ENC_PRV_KEY}" "${var_val_enc}") + fi + + if [[ (-n "${var_val}") ]]; then + config[${key}]="${var_val}" + fi + done + + # Set ORDS Secrets + echo "Saving ORDS credentials" + local -i rc=0 + set_ords_secret "${pool_name}" "db.password" "${config[dbpassword]}" + rc=$((rc + $?)) + set_ords_secret "${pool_name}" "db.adminUser.password" "${config[dbadminuserpassword]}" + rc=$((rc + $?)) + set_ords_secret "${pool_name}" "db.cdb.adminUser.password" "${config[dbcdbadminuserpassword]}" + rc=$((rc + $?)) + + if (( rc > 0 )); then + echo "FATAL: Unable to set configuration for pool \"${pool_name}\"" + return 1 + fi + + return 0 +} + +#------------------------------------------------------------------------------ +ords_upgrade() { + local -r _pool_name="${1}" + local -i _rc=0 + + sub "ORDS install/upgrade" + + if [[ -n "${config[dbadminuser]}" ]]; then + + echo "Performing ORDS install/upgrade as ${config[dbadminuser]} into ${config[dbusername]} on pool ${_pool_name}" + if [[ ${_pool_name} == "default" ]]; then + ords --config "$ORDS_CONFIG" install --db-only \ + --admin-user "${config[dbadminuser]}" --password-stdin <<< "${config["dbadminuserpassword"]}" + _rc=$? + else + ords --config "$ORDS_CONFIG" install --db-pool "${_pool_name}" --db-only \ + --admin-user "${config[dbadminuser]}" --password-stdin <<< "${config["dbadminuserpassword"]}" + _rc=$? + fi + + # Dar be bugs below deck with --db-user so using the above + # ords --config "$ORDS_CONFIG" install --db-pool "$1" --db-only \ + # --admin-user "$ords_admin" --db-user "$ords_user" --password-stdin <<< "${!2}" + else + echo "WARNING: Admin user not set, skipping ORDS install/upgare" + fi + + return $_rc +} + + + +#------------------------------------------------------------------------------ +function get_apex_version() { + local -n _db_apex_version="${1}" + local -i _rc=0 + + sub "APEX version check" + + local -r _ver_sql="SELECT VERSION FROM DBA_REGISTRY WHERE COMP_ID='APEX';" + #local -r _ver_sql="SELECT SCHEMA FROM DBA_REGISTRY WHERE COMP_ID='APEX';" + run_sql "${_ver_sql}" "_db_apex_version" + _rc=$? + + if (( _rc > 0 )); then + echo "FATAL: Unable to get APEX version" + dump_stack + return $_rc + fi + + _db_apex_version=${_db_apex_version//[^0-9.]/} + #_db_apex_version="${_db_apex_version//[[:space:]]}" + if [[ -z "${_db_apex_version}" ]]; then + _db_apex_version="NotInstalled" + fi + echo "Database APEX Version: ${_db_apex_version}" + + return $_rc +} + +#------------------------------------------------------------------------------ +function get_apex_action(){ + local -r _conn_string="${1}" + local -r _db_apex_version="${2}" + local -n _action="${3}" + local -i _rc=0 + + sub "APEX installation check" + + _action="error" + if [[ ( -z "${_db_apex_version}" ) || ( "${_db_apex_version}" == "NotInstalled" ) ]]; then + echo "Installing APEX ${APEX_VER}" + _action="install" + elif apex_compare_versions "${_db_apex_version}" "${APEX_VER}"; then + echo "Upgrading from ${_db_apex_version} to ${APEX_VER}" + _action="upgrade" + else + echo "No Installation/Upgrade Required" + _action="none" + fi + + return $_rc +} + +#------------------------------------------------------------------------------ +function check_apex_installation_version(){ + + sub "APEX installation files" + + if [[ ! (-f ${APEX_VERSION_TXT}) ]]; then + echo "APEX installation not found, missing ${APEX_VERSION_TXT}" + return 1 + fi + + APEX_VER=$(cat "${APEX_VERSION_TXT}"|grep Version|cut -f2 -d:|tr -d '[:space:]') + echo "APEX_VER: ${APEX_VER}" +} + +#------------------------------------------------------------------------------ +function apex_external(){ + sub "APEX external" + + if [[ ${external_apex} != "true" ]]; then + echo "APEX external disabled" + return 0 + fi + + id + df -h "$APEX_INSTALL" + ls -ld "$APEX_INSTALL" + ls -l "$APEX_INSTALL" + ls -l "${APEX_VERSION_TXT}" + echo test >> "$APEX_INSTALL/test.txt" + ls -l "$APEX_INSTALL/test.txt" + + while true + do + date +"%Y-%m-%d %H:%M:%S" + if [[ -f ${APEX_VERSION_TXT} ]] + then + echo Found images/apex_version.txt + break + else + if [[ -f ${APEX_INSTALL}/apex.zip ]] + then + date + echo "Found ${APEX_INSTALL}/apex.zip, extracting ..." + cd "${APEX_INSTALL}" || return + jar xf apex.zip + mv "${APEX_INSTALL}/apex/"* "${APEX_INSTALL}" + date + echo completed + else + date + echo "Missing ${APEX_INSTALL}/apex.zip, manually copy apex.zip in ${APEX_INSTALL} on the init container of the pod" + echo "e.g. kubectl cp /tmp/apex.zip ordssrvs-697c5698d9-q8gxf:/opt/oracle/apex/ -n testcase -c ordssrvs-init" + sleep 5 + fi + fi + done + +} + +#------------------------------------------------------------------------------ +function apex_download(){ + + sub "APEX download" + + if [[ ${download_apex} != "true" ]]; then + echo "APEX download disabled" + return 0 + fi + + mkdir -p "${APEX_INSTALL}" + rm -rf "${APEX_INSTALL:?}/*" + cd /tmp || return + echo "Downloading ${download_url_apex}" + curl -o apex.zip "${download_url_apex}" + echo "Extracting apex.zip" + jar xf apex.zip + mv "/tmp/apex/*" "${APEX_INSTALL}" + + if [[ ! (-f ${APEXINS}) ]]; then + echo "ERROR: ${APEXINS} not found, APEX download failed" + return 1 + fi + echo "APEX_INSTALL: ${APEX_INSTALL}" + + # config can be read-only, it will be set again at command-line ords start + sub "Configuring ORDS images" + echo "APEX_IMAGES: ${APEX_IMAGES}" + ords config set standalone.static.path "${APEX_IMAGES}" + + return 0 +} + +#------------------------------------------------------------------------------ +function apex_upgrade() { + local -r _upgrade_key="${1}" + local -i _rc=0 + + sub "APEX Installation/Upgrade" + + if [[ -z "${APEX_INSTALL}" ]]; then + echo "ERROR: APEX_INSTALL not set" + return 1 + fi + + if [[ ! ( -f ${APEX_INSTALL}/apexins.sql ) ]]; then + echo "ERROR: ${APEX_INSTALL}/apexins.sql not found" + return 1 + fi + + + if [[ "${!_upgrade_key}" = "true" ]]; then + echo "Starting Installation of APEX" + cd "${APEX_INSTALL}" || return 1 + SEC=${config["dbpassword"]} + local -r _install_sql="@apxsilentins.sql SYSAUX SYSAUX TEMP /i/ $SEC $SEC $SEC $SEC" + run_sql "${_install_sql}" "_install_output" + _rc=$? + echo "Installation Output: ${_install_output:?}" + fi + + return $_rc +} + +#------------------------------------------------------------------------------ +function apex_housekeeping(){ + + # check database APEX version regardless of APEX parameters + get_apex_version "db_apex_version" + if [[ -z "${db_apex_version}" ]]; then + echo "FATAL: Unable to get APEX Version for pool \"${pool_name}\"" + return 1 + fi + + # check if apex upgrade is enabled + if [[ "${apex_upgrade}" != "true" ]]; then + echo "APEX Install/Upgrade not requested for pool \"${pool_name}\"" + return 0 + fi + + # get suggested action + get_apex_action "${config[adminconnect]}" "${db_apex_version}" "db_apex_action" + if [[ -z "${db_apex_action}" ]]; then + echo "FATAL: Unable to get APEX suggested action for pool \"${pool_name}\"" + return 1 + fi + + # upgrade + echo "APEX version : \"${db_apex_version}\"" + echo "APEX suggested action : $db_apex_action" + if [[ ${db_apex_action} != "none" ]]; then + apex_upgrade "${config[cloudconfig]}" "${config[adminconnect]}" "${pool_name_underscore}_autoupgrade_apex" + rc=$? + if (( rc > 0 )); then + echo "FATAL: Unable to ${db_apex_action} APEX for pool \"${pool_name}\"" + return 1 + fi + fi + + +} + + + +#------------------------------------------------------------------------------ +function pool_parameters(){ + + sub "pool parameters" + apex_upgrade_var=${pool_name_underscore}_autoupgrade_apex + apex_upgrade=${!apex_upgrade_var} + [[ -z "$apex_upgrade" ]] && apex_upgrade=false + echo "${pool_name} - autoupgrade_apex : ${apex_upgrade}" + + ords_upgrade_var=${pool_name_underscore}_autoupgrade_ords + ords_upgrade=${!ords_upgrade_var} + [[ -z "$ords_upgrade" ]] && ords_upgrade=false + echo "${pool_name} - autoupgrade_ords : ${ords_upgrade}" + + pool_ords_wallet_path="${ORDS_CONFIG}/databases/${pool_name}/wallet" + pool_ords_wallet="${pool_ords_wallet_path}/cwallet.sso" + +} + +pool_admin_setup(){ + + sub "Admin Setup" + # dbadminuserpassword + if [[ (-z "${config[dbadminuserpassword]}") && (-f "${pool_ords_wallet}") ]] + then + ls -l "${pool_ords_wallet}" + echo "Admin password not set and wallet exists, reading admin password from wallet" + PKILIB="/opt/oracle/sqlcl/lib/oraclepki.jar" + PKICLASS="oracle.security.pki.OracleSecretStoreTextUI" + PKIENTRY="db.adminUser.password" + config[dbadminuserpassword]=$(java -cp ${PKILIB} ${PKICLASS} -wrl ${pool_ords_wallet_path} -viewEntry ${PKIENTRY}|grep ${PKIENTRY}|cut -f2 -d\=|cut -f2 -d\ ) + fi + + prepare_pool_connect_string config[connect] "${config[dbconnectiontype]}" "${config[dbadminuser]}" "${config[dbadminuserpassword]}" + if [[ -z "${config[connect]}" ]]; then + echo "Unable to get admin database connect string for pool \"${pool_name}\"" + dump_stack + pool_exit[${pool_name}]=1 + return 1 + fi + + is_adb=false + check_adb "is_adb" + rc=$? + if (( rc > 0 )); then + pool_exit[${pool_name}]=1 + return 1 + fi + + if (( is_adb )); then + # Create ORDS User + echo "Processing ADB in Pool: ${pool_name}" + create_adb_user "${pool_name}" + return 0 + fi + + # not ADB + + # APEX + apex_housekeeping + rc=$? + if (( rc > 0 )); then + echo "FATAL: unable to manage APEX configuration for pool \"${pool_name}\"" + dump_stack + pool_exit[${pool_name}]=1 + return 1 + fi + + # database ORDS + if [[ ${ords_upgrade} == "true" ]]; then + ords_upgrade "${pool_name}" + rc=$? + if (( rc > 0 )); then + echo "FATAL: Unable to perform requested ORDS install/upgrade on pool \"${pool_name}\"" + pool_exit[${pool_name}]=1 + dump_stack + return 1 + fi + else + echo "ORDS Install/Upgrade not requested for pool \"${pool_name}\"" + fi +} + +pool_check(){ + + sub "SQL Test" + + # dbpassword from wallet + if [[ (-z "${config[dbpassword]}" ) && (-f "${pool_ords_wallet}") ]] + then + ls -l "${pool_ords_wallet}" + echo "Password not set and wallet exists, reading password from wallet" + PKILIB="/opt/oracle/sqlcl/lib/oraclepki.jar" + PKICLASS="oracle.security.pki.OracleSecretStoreTextUI" + PKIENTRY="db.password" + config["dbpassword"]=$(java -cp ${PKILIB} ${PKICLASS} -wrl ${pool_ords_wallet_path} -viewEntry ${PKIENTRY}|grep ${PKIENTRY}|cut -f2 -d\=|cut -f2 -d\ ) + fi + + if [[ (-z ${config[dbpassword]} ) ]] + then + echo "WARNING: dbpassword not set" + fi + + prepare_pool_connect_string config[connect] "${config[dbconnectiontype]}" "${config[dbusername]}" "${config[dbpassword]}" + if [[ -z ${config[connect]} ]]; then + echo "Unable to get database connect string for pool \"${pool_name}\"" + return 1 + fi + + local -r _sqlcheck=" + set lines 1000 pages 100 feed off + WHENEVER SQLERROR EXIT SQL.ERROR + WHENEVER OSERROR EXIT 1 + alter session set nls_date_format='dd/mm/yyyy hh24:mi:ss'; + SELECT + SYSDATE, + SYS_CONTEXT('USERENV','SERVER_HOST') AS server_host, + USER, + SYS_CONTEXT('USERENV','INSTANCE_NAME') AS instance_name, + SYS_CONTEXT('USERENV','CON_NAME') AS current_pdb + FROM dual; + prompt + select banner from v\$version; + " + echo "Checking sql connection for pool ${pool_name}" + run_sql "${_sqlcheck}" _checkoutput on + _rc=$? + + echo "--------------------------------------------------------------------------------------------------" + echo "${_checkoutput}" + echo + echo "--------------------------------------------------------------------------------------------------" + + if (( _rc > 0 )); then + echo "SQL check FAILED" + return $_rc + fi + + echo "SQL check SUCCESS" +} + +#------------------------------------------------------------------------------ +# INIT +#------------------------------------------------------------------------------ +declare -A pool_exit +sep +sub "ORDSSRVS init" +sep + +global_parameters +ords_client_version + +apex_download +apex_external + +# check APEX installation files version, downloaded or mounted by PVC +check_apex_installation_version + +if [[ !( -d "${ORDS_CONFIG}/databases/" ) ]] +then + echo "No database pools found under ${ORDS_CONFIG}/databases" + echo "POOLERRORS 0 POOLS 0" + echo "init end" + exit 0 +fi + +for pool in "${ORDS_CONFIG}/databases/"*; do + rc=0 + pool_name=$(basename "$pool") + pool_name_underscore=${pool_name//-/_} + pool_exit[${pool_name}]=0 + ords_cfg_cmd="ords --config $ORDS_CONFIG config --db-pool ${pool_name}" + declare -A config=() + + sep + sub "Pool ${pool_name}" + echo "poolName: ${pool_name}" + + pool_parameters + + setup_credentials + rc=$? + if (( rc > 0 )); then + pool_exit[${pool_name}]=1 + continue + fi + + setup_sql_environment + rc=$? + if (( rc > 0 )); then + pool_exit[${pool_name}]=1 + continue + fi + + sub "Pool Admin setup" + echo "pool : ${pool_name}" + echo "dbadminuser : ${config[dbadminuser]}" + echo "dbconnectiontype : ${config[dbconnectiontype]}" + if [[ (-n "${config[dbadminuser]}") ]] + then + pool_admin_setup + rc=$? + if (( rc > 0 )); then + pool_exit[${pool_name}]=1 + continue + fi + else + echo "Admin user not set, skipping admin setup" + fi + + # reset conncetion string after admin operations + config[connect]="" + + sub "Pool Connection check" + echo "pool : ${pool_name}" + echo "dbusername : ${config[dbusername]}" + echo "dbconnectiontype : ${config[dbconnectiontype]}" + echo "cloudconfig : ${config[cloudconfig]}" + pool_check + rc=$? + if (( rc > 0 )); then + pool_exit[${pool_name}]=1 + continue + fi + +done + +sep +sub "Exit codes" +rc=1 +pools=0 +poolerrors=0 +for key in "${!pool_exit[@]}"; do + printf "Pool: %-16s Exit Code: %s\n" "${key}" "${pool_exit[${key}]}" + + pools=$(( pools + 1 )) + + # if at least one pool is ok, the return code is 0 + if (( ${pool_exit[$key]} == 0 )); then + rc=0 + else + poolerrors=$(( poolerrors + 1 )) + fi +done + +echo POOLERRORS $poolerrors POOLS $pools +echo "init end" +exit $rc diff --git a/ordssrvs/ords_start.sh b/ordssrvs/ords_start.sh new file mode 100644 index 00000000..a8c9691a --- /dev/null +++ b/ordssrvs/ords_start.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +date +"%Y-%m-%d %H:%M:%S" +echo "=== ORDS start ===" +echo "ORDS_CONFIG: ${ORDS_CONFIG}" + +if [[ -n "${central_config_url-}" ]]; then + echo "Starting ORDS using Central Config" + echo "central_config_url : ${central_config_url}" + echo "central_config_wallet : ${central_config_wallet}" + #ords --java-options "-Dconfig.url=${central_config_url} -Dconfig.wallet=${central_config_wallet}" serve + unset ORDS_CONFIG + ords --java-options "-Dconfig.url=${central_config_url}" serve + exit $? +fi + +unset APEX_IMAGES + +# old path, until ORDS image 24.1.1 +if [[ ! (-z ${APEX_BASE}) && ! (-z ${APEX_VER}) && (-d ${APEX_BASE}/${APEX_VER}/images) ]]; then + APEX_IMAGES=${APEX_BASE}/${APEX_VER}/images +fi + +# downloaded image path +if [[ -d /opt/oracle/apex/images ]]; then + APEX_IMAGES=/opt/oracle/apex/images +fi + +if [[ -z ${APEX_IMAGES} ]]; then + echo "APEX_IMAGES not found" + ords --config "${ORDS_CONFIG}" serve +else + echo "APEX_IMAGES: ${APEX_IMAGES}" + ords --config "${ORDS_CONFIG}" serve --apex-images "${APEX_IMAGES}" +fi +