/* Copyright 2022. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package providerctl import ( "bytes" "context" "fmt" "os" "path/filepath" "reflect" "sort" "strconv" "strings" "time" "github.com/fluxcd/pkg/ssa" "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/cli-utils/pkg/kstatus/polling" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" logger "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/kustomize/api/hasher" kp "sigs.k8s.io/kustomize/api/provider" "sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/kustomize/kyaml/resid" apiv1 "k8s.io/api/core/v1" "edge-infra.dev/pkg/edge/info" edgeConditions "edge-infra.dev/pkg/k8s/runtime/conditions" "edge-infra.dev/pkg/k8s/runtime/controller/metrics" api "edge-infra.dev/pkg/edge/iam/api/v1alpha1" "edge-infra.dev/pkg/edge/iam/config" ff "edge-infra.dev/pkg/lib/featureflag" ) const ( SecretExistCheckFailure = "SecretExistCheckFailure" SecretCreationFailure = "SecretCreationFailure" MissingSecretData = "MissingSecretData" ExternalProvider = "external-provider" ProviderContainer = "edge-iam-provider" DaemonSet = "DaemonSet" Deployment = "Deployment" ClientCRDName = "Client" ClientCRDVersion = "v1alpha1" ClientCRDGroup = "iam.edge-infra.dev" TouchpointProvider = "touchpoint-provider" StoreProvider = "store-provider" TestClient = "test-client" TestClientCRName = "verify" TestClientSecretName = "verify-creds" EncryptionRotationSucceeded = "EncryptionRotationSucceeded" EdgeIAM = "edge-iam" EncryptionKeySecretPrefix = "id-encryption-key-" ) var envsToSync = [...]string{ "IAM_PROFILE_LIFESPAN", "IAM_BARCODE_LIFESPAN", "FF_BARCODE_ENFORCE_ROLES", "FF_ENFORCE_PERMISSIONS", "IAM_PIN_RETRY_THRESHOLD", "IAM_PIN_HISTORY_LENGTH", "IAM_PIN_LIFESPAN", } // ProviderReconciler reconciles a Provider object type ProviderReconciler struct { client.Client Scheme *runtime.Scheme // Name is the controller's name, used to consistently represent the controller // in various cluster interactions, e.g., as field manager Name string // Manifests is ? Manifests []*unstructured.Unstructured // ResourceManager is a server-side apply client that can poll for the resources // we are applying to the server ResourceManager *ssa.ResourceManager // kubebuilder default metrics Metrics metrics.Metrics // Resmaps is a map of resmap.Resmap keyed by the target env Resmaps map[string]resmap.ResMap } //+kubebuilder:rbac:groups=iam.edge-infra.dev,resources=providers,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=iam.edge-infra.dev,resources=providers/status,verbs=get;update;patch //+kubebuilder:rbac:groups=iam.edge-infra.dev,resources=providers/finalizers,verbs=update // +kubebuilder:rbac:groups="",resources=pods;serviceaccounts;secrets;configmaps;services;namespaces,verbs=create;get;list;update;patch;watch;delete // +kubebuilder:rbac:groups="",resources=services/status;namespaces/status,verbs=get // +kubebuilder:rbac:groups="apps",resources=deployments;daemonsets;statefulsets;replicasets,verbs=create;get;list;update;patch;watch;delete // +kubebuilder:rbac:groups="getambassador.io",resources=mappings,verbs=create;get;list;update;patch;watch;delete // +kubebuilder:rbac:groups="policy.linkerd.io",resources=servers;serverauthorizations,verbs=create;get;list;update;patch;watch;delete // +kubebuilder:rbac:groups="external-secrets.io",resources=externalsecrets,verbs=get;watch;create;patch;update;list;delete // +kubebuilder:rbac:groups="batch",resources=cronjobs,verbs=create;list;patch // +kubebuilder:rbac:groups="monitoring.coreos.com",resources=servicemonitors,verbs=create;get;list;update;patch;watch;delete // +kubebuilder:rbac:groups="edge.ncr.com",resources=persistence,verbs=get;watch;create;patch;update;list // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // TODO(user): Modify the Reconcile function to compare the state specified by // the Provider object against the actual cluster state, and then // perform operations to make the cluster state reflect the state specified by // the user. // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.2/pkg/reconcile func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { var ( reconcileStart = time.Now() log = logger.FromContext(ctx) provider = api.Provider{} ) //log := logger.FromContext(ctx) log.V(2).Info("reconcile provider") defer func() { r.Metrics.RecordReadiness(ctx, &provider) r.Metrics.RecordReconciling(ctx, &provider) r.Metrics.RecordStalled(ctx, &provider) r.Metrics.RecordDuration(ctx, &provider, reconcileStart) }() // used to retain ownership on resources (so it doesnt get fluxed away) r.setResourceManager() // try to get the provider to reconcile err := r.Get(ctx, req.NamespacedName, &provider) if err != nil { return ctrl.Result{}, err } // reconcile the provider object, creating a copy so that we avoid mutating our // controller's cache reconciled, reconcileErr := r.reconcile(ctx, req, *provider.DeepCopy()) // reflect the reconciled status on the API server if err := r.updateStatus(ctx, req, reconciled.Status); err != nil { log.Error(err, "unable to update status") return ctrl.Result{Requeue: true}, nil } // if reconciliation errors occur, return them and requeue so we re-try // if there are reconciliation errors that are nonrecoverable, we can handle // them here by setting Requeue: false conditionally if reconcileErr != nil { log.Error(reconcileErr, "error reconciling provider resource") return ctrl.Result{Requeue: true}, nil } // by default, we want to reconcile every 5 minutes to be sure that our // linkerd installation manifests are not drifting or deleted by another // actor on the cluster return ctrl.Result{RequeueAfter: 5 * time.Minute}, nil } // SetupWithManager sets up the controller with the Manager. func (r *ProviderReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). // TODO should we builder.WithPredicates(predicate.GenerationChangedPredicate{}? For(&api.Provider{}). Complete(r) } func (r *ProviderReconciler) setResourceManager() { if r.ResourceManager == nil { mgr := ssa.NewResourceManager( r.Client, // be sure to consistently communicate this controllers ownership of objects // this should match the result of createOpts() polling.NewStatusPoller(r.Client, r.Client.RESTMapper(), polling.Options{}), ssa.Owner{Field: r.Name, Group: config.Domain()}, ) r.ResourceManager = mgr } } func (r *ProviderReconciler) reconcile(ctx context.Context, req ctrl.Request, provider api.Provider) (api.Provider, error) { var err error // provider, err = r.reconcileNamespace(ctx, provider) // if err != nil { // return provider, err // } provider, err = r.reconcileSecrets(ctx, req, provider) if err != nil { return provider, err } provider, err = r.reconcileEncryptionKeyRotation(ctx, provider) if err != nil { return provider, err } provider, err = r.reconcileTarget(ctx, provider) if err != nil { return provider, err } // reconciled without errors.. if !meta.IsStatusConditionFalse(provider.Status.Conditions, api.ReadyCondition) { return api.Ready(provider, "InstallationSucceeded", "successfully installed the provider"), nil } return api.Provider{}, nil } func (r *ProviderReconciler) reconcileSecrets(ctx context.Context, req ctrl.Request, provider api.Provider) (api.Provider, error) { var pkSecret, challengeSecret *apiv1.Secret var err error log := logger.FromContext(ctx) provider, pkSecret, err = r.reconcilePrivateKeysSecret(ctx, req, provider) if err != nil { return provider, err } // secret exists, let's validate it provider, err = r.validatePrivateKeysSecret(provider, pkSecret) if err != nil { log.Error(err, fmt.Sprintf("secret %s/%s is invalid", pkSecret.Name, pkSecret.Namespace)) return provider, err } provider, challengeSecret, err = r.reconcileChallengeSecret(ctx, req, provider) if err != nil { return provider, err } // secret exists, let's validate it provider, err = r.validateChallengeSecret(provider, challengeSecret) if err != nil { log.Error(err, fmt.Sprintf("secret %s/%s is invalid", challengeSecret.Name, challengeSecret.Namespace)) return provider, err } return provider, nil } // func (r *ProviderReconciler) reconcileNamespace(ctx context.Context, provider api.Provider) (api.Provider, error) { // namespace := &corev1.Namespace{ // ObjectMeta: metav1.ObjectMeta{ // Name: config.Namespace(), // OwnerReferences: r.ownerRef(&provider), // Labels: map[string]string{ // // this gets us our pull-secret // "workload.edge.ncr.com": "platform", // }, // }, // } // options := &client.CreateOptions{ // FieldManager: config.FieldManager(r.Name), // } // err := r.Create(ctx, namespace, options) // if err != nil && !errors.IsAlreadyExists(err) { // return api.NotReady(provider, "NamespaceCreationFailed", err.Error()), err // } // return provider, nil // } func (r *ProviderReconciler) deleteResource(ctx context.Context, resourceName string, gvk resid.Gvk, resMap resmap.ResMap, provider api.Provider) error { resourceID := resid.ResId{ Gvk: gvk, Name: resourceName, Namespace: provider.Namespace, } objectToBeDeleted, err := resMap.GetById(resourceID) if err != nil { return err } resource, err := objectToBeDeleted.AsYAML() if err != nil { return err } manifests, err := ssa.ReadObjects(bytes.NewReader(resource)) if err != nil { return err } sort.Sort(ssa.SortableUnstructureds(manifests)) for i := range manifests { manifests[i].SetOwnerReferences(r.ownerRef(&provider)) } _, err = r.ResourceManager.DeleteAll(ctx, manifests, ssa.DefaultDeleteOptions()) if err != nil { return err } err = resMap.Remove(resourceID) return err } func (r *ProviderReconciler) reconcileTarget(ctx context.Context, provider api.Provider) (api.Provider, error) { log := logger.FromContext(ctx) target := provider.Spec.Target if target == "d-edge-lab" { target = "" } // if spec.target is empty, detect the cluster type by retrieving edge-info // apply target env based on cluster types if target == "" { clusterType := "" if einfo, _ := info.FromClient(ctx, r.Client); einfo != nil { clusterType = einfo.ClusterType } switch clusterType { case "dsds": target = "d-edge" case "sds": target = "edge" default: target = "generic" } } log.V(2).Info("reconcile target", "target", target) if !config.TestClientEnabled() { //nolint // Check if copy already exists if it does use that instead of removing test-client resources again. _, ok := r.Resmaps[target+"-without-test-client"] // Create resmap copy without test client resources. if !ok { orgResMap, ok := r.Resmaps[target] if !ok { return api.NotReady(provider, "ResMapFailed", fmt.Sprintf("no resources found for target %s", target)), fmt.Errorf("no resources found for target %s", target) } // Create a copy that can be used by reconiliations (for create test client false) after the first one for optimization. noTestClientResMap := orgResMap.DeepCopy() err := r.deleteResource(ctx, TestClient, resid.Gvk{Version: "v1", Kind: "ServiceAccount"}, noTestClientResMap, provider) if err != nil { log.Error(err, "delete test client resource: no resources found for ServiceAccount test-client") } err = r.deleteResource(ctx, TestClient, resid.Gvk{Version: "v1", Kind: "Service"}, noTestClientResMap, provider) if err != nil { log.Error(err, "delete test client resource: no resources found for Service test-client") } err = r.deleteResource(ctx, TestClient, resid.Gvk{Version: "v1", Kind: Deployment, Group: "apps"}, noTestClientResMap, provider) if err != nil { log.Error(err, "delete test client resource: no resources found for Deployment test-client") } err = r.deleteResource(ctx, TestClientCRName, resid.Gvk{Version: ClientCRDVersion, Kind: ClientCRDName, Group: ClientCRDGroup}, noTestClientResMap, provider) if err != nil { log.Error(err, "delete test client resource: no resources found for Client CR of test-client") } err = r.deleteResource(ctx, TestClientSecretName, resid.Gvk{Version: "v1", Kind: "Secret"}, noTestClientResMap, provider) if err != nil { log.Error(err, "delete test client resource: no resources found for secret of test-client") } r.Resmaps[target+"-without-test-client"] = noTestClientResMap } target = target + "-without-test-client" } else { delete(r.Resmaps, target+"-without-test-client") } resMap, ok := r.Resmaps[target] if !ok { return api.NotReady(provider, "ResMapFailed", fmt.Sprintf("no resources found for target %s", target)), fmt.Errorf("no resources found for target %s", target) } syncDeploymentErr := updateEnvVarsForResource("deployment", provider, resMap, log) if syncDeploymentErr != nil { log.Info("failed to sync deployment environment variables with provider", "error", syncDeploymentErr) } syncDaemonsetErr := updateEnvVarsForResource("daemonset", provider, resMap, log) if syncDaemonsetErr != nil { log.Info("failed to sync deamonset environment variables with provider", "error", syncDaemonsetErr) } resources, err := resMap.AsYaml() if err != nil { return api.NotReady(provider, "KustomizeToYamlFailed", err.Error()), err } r.Manifests, err = ssa.ReadObjects(bytes.NewReader(resources)) if err != nil { return api.NotReady(provider, "SSAReadObjectsFailed", err.Error()), err } sort.Sort(ssa.SortableUnstructureds(r.Manifests)) for i := range r.Manifests { r.Manifests[i].SetOwnerReferences(r.ownerRef(&provider)) } if provider.Spec.Okta.CredsSecretName != "" { //oktaAddSecret checks if the manifest needs secretRef for okta and adds if so oktaAddSecret(r.Manifests, log) //this should override the secret name in the external secret with credsSecretName from provider which contains uuid oktaReplaceSecretName(r.Manifests, provider) } else { //removes okta external secret if credsSecretName is empty r.Manifests = oktaRemoveExternalSecret(r.Manifests) } // get the encryption version the databases are using (if any) version := encryptionGetDatabaseVersion(provider.Status) if version != "" { //encryptionAddSecretVersion adds the encryption key version to the IAM_ENCRYPTION_KEY env r.Manifests = encryptionAddSecretVersion(version, r.Manifests, log) } else { //removes encryption key secret if encryption is not enabled/no version found r.Manifests = encryptionRemoveSecretEnv(r.Manifests, log) } // TODO: aggregate errors to reduce `if` spam // apply the objects in order based on their dependencies, e.g., Namespaces first changed, err := r.ResourceManager.ApplyAllStaged(ctx, r.Manifests, ssa.ApplyOptions{ Force: true, WaitTimeout: 60 * time.Second, }) if err != nil { return api.NotReady(provider, "ResourceManagerApplyFailed", err.Error()), err } log.V(2).Info("applied target manifests", "resources", changed.Entries) // wait for them to reconcile // placed around if statement so that integration tests dont timeout waiting for api server if !config.IsTest() { err = r.ResourceManager.WaitForSet(changed.ToObjMetadataSet(), ssa.WaitOptions{ Interval: 2 * time.Second, Timeout: 60 * time.Second, }) if err != nil { return api.NotReady(provider, "ResourceManagerTimedOutWaitingForSet", err.Error()), err } } log.Info("target manifests successfully reconciled") return provider, nil } func oktaAddSecret(manifest []*unstructured.Unstructured, log logr.Logger) { for i := range manifest { if ((manifest[i].GetName() == StoreProvider) || (manifest[i].GetName() == ExternalProvider) || (manifest[i].GetName() == TouchpointProvider)) && (manifest[i].GetKind() == Deployment || manifest[i].GetKind() == DaemonSet) && manifest[i].GetNamespace() == EdgeIAM { containers := manifest[i].Object["spec"].(map[string]interface{})["template"].(map[string]interface{})["spec"].(map[string]interface{})["containers"].([]interface{}) for _, container := range containers { if container.(map[string]interface{})["name"] != ProviderContainer { continue } // get the 'envFrom' map envFrom := container.(map[string]interface{})["envFrom"].([]interface{}) oktaSecretRef := map[string]interface{}{"secretRef": map[string]interface{}{"name": "okta-secret"}} envFrom = append(envFrom, oktaSecretRef) container.(map[string]interface{})["envFrom"] = envFrom log.Info("Added okta secretRef") } } } } func oktaReplaceSecretName(manifest []*unstructured.Unstructured, provider api.Provider) { for i := range manifest { if (manifest[i].GetName() == "okta-secret") && (manifest[i].GetKind() == "ExternalSecret") { datafrom := manifest[i].Object["spec"].(map[string]interface{})["dataFrom"].([]interface{}) for _, key := range datafrom { if key.(map[string]interface{})["extract"].(map[string]interface{})["key"].(string) == "okta-creds-0" { key.(map[string]interface{})["extract"].(map[string]interface{})["key"] = provider.Spec.Okta.CredsSecretName break } } } } } func oktaRemoveExternalSecret(manifests []*unstructured.Unstructured) []*unstructured.Unstructured { for i := range manifests { if (manifests[i].GetName() == "okta-secret") && (manifests[i].GetKind() == "ExternalSecret") { manifests = append(manifests[:i], manifests[i+1:]...) break } } return manifests } func getProviderValueFor(provider api.Provider, envName string) string { switch envName { case "IAM_PIN_RETRY_THRESHOLD": if provider.Spec.PIN.Attempts != 0 { return strconv.Itoa(int(provider.Spec.PIN.Attempts)) } return "" case "IAM_PIN_HISTORY_LENGTH": if provider.Spec.PIN.History != 0 { return strconv.Itoa(int(provider.Spec.PIN.History)) } return "" case "IAM_PIN_LIFESPAN": return provider.Spec.PIN.Expire case "IAM_PROFILE_LIFESPAN": return provider.Spec.Profile.Expire case "IAM_BARCODE_LIFESPAN": return provider.Spec.Barcode.Expire case "FF_BARCODE_ENFORCE_ROLES": if provider.Spec.Barcode.Role { return "true" } return "" case "FF_ENFORCE_PERMISSIONS": if len(provider.Spec.FF) > 0 { if v, ok := provider.Spec.FF[ff.EnforceEdgeIDPermissions]; ok { return strconv.FormatBool(v) } return "" } return "" default: } return "" } func updateEnvVarsForResource(resType string, provider api.Provider, resMap resmap.ResMap, log logr.Logger) error { // define the resource ID var gvk resid.Gvk var resourceName string if resType == "deployment" { gvk = resid.Gvk{Version: "v1", Kind: Deployment, Group: "apps"} resourceName = StoreProvider } else if resType == "daemonset" { gvk = resid.Gvk{Version: "v1", Kind: "DaemonSet", Group: "apps"} resourceName = TouchpointProvider } else { return fmt.Errorf("unsupported resource type %s", resType) } resourceID := resid.ResId{ Gvk: gvk, Name: resourceName, Namespace: provider.Namespace, } // get the resource resource, err := resMap.GetById(resourceID) if err != nil { log.Info("failed to find the resource in the resources list. will try to sync it again soon.", "error", err) return fmt.Errorf("failed to find the resource in the resources list. will try to sync it again soon") } // get the unstructured resource so we can edit it unstructured, _ := resource.Map() // get the 'containers' part, where the `env` section is located... containers, ok := unstructured["spec"].(map[string]interface{})["template"].(map[string]interface{})["spec"].(map[string]interface{})["containers"].([]interface{}) if !ok { return fmt.Errorf("failed to get containers for %s", resType) } for _, container := range containers { // make sure we work only on the right container if container.(map[string]interface{})["name"] != ProviderContainer { continue } // get the 'env' map env, ok := container.(map[string]interface{})["env"].([]interface{}) if !ok { continue } var exists bool // iterate the environment variables that we are interested in and look for changes for _, envCandidate := range envsToSync { exists = false for j, e := range env { name, ok := e.(map[string]interface{})["name"].(string) if ok && name == envCandidate { // Update the existing environment variable with the matching value in the provider exists = true val := getProviderValueFor(provider, name) if val != "" { e.(map[string]interface{})["value"] = val } else { env = append(env[:j], env[j+1:]...) } break } } if !exists { // if the environment variable does not exist, create a new one envValue := getProviderValueFor(provider, envCandidate) if envValue != "" { env = append(env, map[string]interface{}{ "name": envCandidate, "value": envValue, }) } container.(map[string]interface{})["env"] = env } } } // create resource back from the unstructured deployment depProvider := kp.NewDefaultDepProvider() rf := depProvider.GetResourceFactory() updatedResource := rf.FromMap(unstructured) // make sure that the latest resource is set _, err = resMap.Replace(updatedResource) if err != nil { return fmt.Errorf("failed to replace "+resType+"resource. ", err) } log.V(2).Info(resType + " is synced with provider") return nil } func (r *ProviderReconciler) ownerRef(provider *api.Provider) []metav1.OwnerReference { kind := reflect.TypeOf(api.Provider{}).Name() ownerRef := []metav1.OwnerReference{ *metav1.NewControllerRef( provider, api.GroupVersion.WithKind(kind), ), } return ownerRef } // updateStatus fetches an up-to-date copy of the object we want to update the // status for and patches its status. // this is done to minimize cache mutation errors and API server mismatch errors // that can occur if the patch does not align with the API server's current state func (r *ProviderReconciler) updateStatus(ctx context.Context, req ctrl.Request, status api.ProviderStatus) error { var provider api.Provider if err := r.Get(ctx, req.NamespacedName, &provider); err != nil { return err } providerStatus := provider.Status.Conditions patch := client.MergeFrom(provider.DeepCopy()) provider.Status = status // getting most recent EncryptionRotationSucceeded status from the most recent provider grabbed // to make sure it's not overridden by the older one that mightve been passed in for i, condition := range providerStatus { if condition.Reason == EncryptionRotationSucceeded { if provider.Spec.Encryption.Version != "" { edgeConditions.Set(&provider, &condition) //nolint:gosec } else { // remove the condition from the list provider.Status.Conditions = append(providerStatus[:i], providerStatus[i+1:]...) } } } return r.Status().Patch(ctx, &provider, patch) } // createResmap reads the kustomized manifests file at the given path and // returns a resmap created from its bytes. // This is used to populate the Resmaps field func CreateResmaps(paths []string) (map[string]resmap.ResMap, error) { factory := resmap.NewFactory(resource.NewFactory(&hasher.Hasher{})) resMaps := make(map[string]resmap.ResMap) for _, path := range paths { readManifests, err := os.Open(path) if err != nil { return nil, err } b, err := os.ReadFile(readManifests.Name()) if err != nil { return nil, err } resMap, err := factory.NewResMapFromBytes(b) if err != nil { return nil, err } dir := filepath.Dir(path) key := strings.Split(dir, "/") resMaps[key[len(key)-1]] = resMap } return resMaps, nil } // grab the status version the databases are using, if none found, return empty string func encryptionGetDatabaseVersion(status api.ProviderStatus) string { for _, condition := range status.Conditions { if condition.Reason == EncryptionRotationSucceeded { message := condition.Message version := strings.TrimPrefix(message, "successfully updated databases to version: ") return version } } return "" } // for store-provider, touchpoint-provider, & operator, edit the IAM_ENCRYPTION_KEY env to add the secret name func encryptionAddSecretVersion(version string, manifest []*unstructured.Unstructured, log logr.Logger) []*unstructured.Unstructured { secretName := EncryptionKeySecretPrefix + version for i := range manifest { name := manifest[i].GetName() // if its a store/touchpoint/external provider if ((name == StoreProvider) || (name == ExternalProvider) || (name == TouchpointProvider)) && (manifest[i].GetKind() == Deployment || manifest[i].GetKind() == DaemonSet) && manifest[i].GetNamespace() == EdgeIAM { containers := manifest[i].Object["spec"].(map[string]interface{})["template"].(map[string]interface{})["spec"].(map[string]interface{})["containers"].([]interface{}) // get edge-iam-provider container for _, container := range containers { containerName := container.(map[string]interface{})["name"] if containerName == ProviderContainer { // get the 'env' map envs := container.(map[string]interface{})["env"].([]interface{}) for _, env := range envs { envName := env.(map[string]interface{})["name"] // add the iam encryption key env secretname if envName == "IAM_ENCRYPTION_KEY" { env.(map[string]interface{})["valueFrom"].(map[string]interface{})["secretKeyRef"].(map[string]interface{})["name"] = secretName // todo: do i need the following line // container.(map[string]interface{})["env"] = envs log.Info(fmt.Sprintf("Updated encryption key env on %s to use the encryption key secret", name), "secret", secretName) break } } } } } } return manifest } // for store-provider, touchpoint-provider, & operator, edit obj to remove the IAM_ENCRYPTION_KEY env func encryptionRemoveSecretEnv(manifest []*unstructured.Unstructured, log logr.Logger) []*unstructured.Unstructured { for i := range manifest { name := manifest[i].GetName() if ((name == StoreProvider) || (name == ExternalProvider) || (name == TouchpointProvider)) && (manifest[i].GetKind() == Deployment || manifest[i].GetKind() == DaemonSet) && manifest[i].GetNamespace() == EdgeIAM { containers := manifest[i].Object["spec"].(map[string]interface{})["template"].(map[string]interface{})["spec"].(map[string]interface{})["containers"].([]interface{}) for _, container := range containers { containerName := container.(map[string]interface{})["name"] if containerName == ProviderContainer { // get the 'env' map envs := container.(map[string]interface{})["env"].([]interface{}) for j, env := range envs { envName := env.(map[string]interface{})["name"] // remove the iam encryption key env from the list if envName == "IAM_ENCRYPTION_KEY" { envs = append(envs[:j], envs[j+1:]...) container.(map[string]interface{})["env"] = envs log.Info(fmt.Sprintf("removed encryption key env on %s to use the encryption key secret", name), "object", name) break } } } } } } return manifest }