Repository: EnMasseProject/enmasse (java)

Issue:
Allow setting security context of pods using persistent volumes
**Overview**

In order to support Ceph persistent volume claims, pods may need to be configured with a security context. This affects components such as:

* standard authservice
* brokered address space broker
* standard address space broker

For the brokers, the podTemplate.spec in the standardinfraconfig/brokeredinfraconfig can be extended to specify the security context.

For standard-authservice, the syntax of AuthenticationService resource needs to be extended.

Pull request description:
Fix #2714: Allow setting security context of pods using persistent volumes
### Type of change

- Enhancement / new feature

### Description

Allows the security context of pods that use a persistent volume to be specified by
way of the CR that controls the resource.


### Checklist

<!--

_Please go through this checklist and make sure all applicable tasks have been done_

-->

- [ ] Update/write design documentation in `./documentation/design`
- [ ] Write tests and make sure they pass
- [ ] Update documentation
- [ ] Check RBAC rights for Kubernetes / OpenShift roles
- [ ] Try your changes from Pod inside your Kubernetes and OpenShift cluster, not just locally
- [ ] Reference relevant issue(s) and close them after merging
- [x] Update CHANGELOG.md


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 01c4ae290..a5813bb03 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+## 0.31.0
+*  #2714: Allow setting security context of pods using persistent volumes
+
 ## 0.30.0
 * Fix go tool vet on newer versions of go
 * #3247: Fix path to probe endpoint used when running on Kubernetes
diff --git a/address-space-controller/src/main/java/io/enmasse/controller/TemplateInfraResourceFactory.java b/address-space-controller/src/main/java/io/enmasse/controller/TemplateInfraResourceFactory.java
index 489836f19..2daf4829d 100644
--- a/address-space-controller/src/main/java/io/enmasse/controller/TemplateInfraResourceFactory.java
+++ b/address-space-controller/src/main/java/io/enmasse/controller/TemplateInfraResourceFactory.java
@@ -23,11 +23,9 @@ import java.io.UncheckedIOException;
 import java.nio.file.Files;
 import java.util.*;
 
-import static io.enmasse.address.model.KubeUtil.applyPodTemplate;
-import static io.enmasse.address.model.KubeUtil.lookupResource;
+import static io.enmasse.address.model.KubeUtil.*;
 
 public class TemplateInfraResourceFactory implements InfraResourceFactory {
-    private static final String FS_GROUP_OVERRIDE = "FS_GROUP_OVERRIDE";
     private static final Logger log = LoggerFactory.getLogger(TemplateInfraResourceFactory.class);
     private final String WELL_KNOWN_CONSOLE_SERVICE_NAME = "console";
 
@@ -35,14 +33,12 @@ public class TemplateInfraResourceFactory implements InfraResourceFactory {
     private final AuthenticationServiceResolver authenticationServiceResolver;
     private final Map<String, String> env;
     private final SchemaProvider schemaProvider;
-    private final Long fsGroupOverride;
 
     public TemplateInfraResourceFactory(Kubernetes kubernetes, AuthenticationServiceResolver authenticationServiceResolver, Map<String, String> env, SchemaProvider schemaProvider) {
         this.kubernetes = kubernetes;
         this.authenticationServiceResolver = authenticationServiceResolver;
         this.env = env;
         this.schemaProvider = schemaProvider;
-        this.fsGroupOverride = getFsGroupOverride();
     }
 
     private void prepareParameters(AddressSpace addressSpace,
@@ -184,10 +180,7 @@ public class TemplateInfraResourceFactory implements InfraResourceFactory {
         setIfEnvPresent(parameters, TemplateParameter.BROKER_PLUGIN_IMAGE);
         setIfEnvPresent(parameters, TemplateParameter.TOPIC_FORWARDER_IMAGE);
         setIfEnvPresent(parameters, TemplateParameter.IMAGE_PULL_POLICY);
-
-        if (fsGroupOverride != null) {
-            parameters.put(TemplateParameter.FS_GROUP_OVERRIDE, fsGroupOverride.toString());
-        }
+        setIfEnvPresent(parameters, TemplateParameter.FS_GROUP_FALLBACK_MAP);
 
         Map<String, String> infraAnnotations = standardInfraConfig.getMetadata().getAnnotations();
         String templateName = getAnnotation(infraAnnotations, AnnotationKeys.TEMPLATE_NAME, "standard-space-infra");
@@ -290,25 +283,11 @@ public class TemplateInfraResourceFactory implements InfraResourceFactory {
             PodTemplateSpec actualPodTemplate = brokerDeployment.getSpec().getTemplate();
             applyPodTemplate(actualPodTemplate, podTemplate);
         }
-
-        if (this.fsGroupOverride != null) {
-            KubeUtil.applyFsGroupOverride(Collections.singletonList(brokerDeployment), this.fsGroupOverride);
-        }
+        overrideFsGroup(brokerDeployment.getSpec().getTemplate(), "broker", kubernetes.getNamespace());
 
         return items;
     }
 
-    private Long getFsGroupOverride() {
-        Long fsGroupOverride = null;
-        if (env.containsKey(FS_GROUP_OVERRIDE)) {
-            try {
-                fsGroupOverride = Long.parseLong(env.get(FS_GROUP_OVERRIDE));
-            } catch (NumberFormatException ignore) {
-            }
-        }
-        return fsGroupOverride;
-    }
-
     private List<HasMetadata> applyStorageClassName(String storageClassName, List<HasMetadata> items) {
         if (storageClassName != null) {
             for (HasMetadata item : items) {
diff --git a/address-space-controller/src/main/java/io/enmasse/controller/common/TemplateParameter.java b/address-space-controller/src/main/java/io/enmasse/controller/common/TemplateParameter.java
index a2a94301c..31b86fb7d 100644
--- a/address-space-controller/src/main/java/io/enmasse/controller/common/TemplateParameter.java
+++ b/address-space-controller/src/main/java/io/enmasse/controller/common/TemplateParameter.java
@@ -48,5 +48,5 @@ public interface TemplateParameter {
     String CONSOLE_OAUTH_DISCOVERY_URL = "CONSOLE_OAUTH_DISCOVERY_URL";
     String CONSOLE_OAUTH_SCOPE = "CONSOLE_OAUTH_SCOPE";
     String CONSOLE_LINK = "CONSOLE_LINK";
-    String FS_GROUP_OVERRIDE = "FS_GROUP_OVERRIDE";
+    String FS_GROUP_FALLBACK_MAP = "FS_GROUP_FALLBACK_MAP";
 }
diff --git a/address-space-controller/src/main/resources/templates/standard-space-infra.yaml b/address-space-controller/src/main/resources/templates/standard-space-infra.yaml
index 3b532763f..9036d65ed 100644
--- a/address-space-controller/src/main/resources/templates/standard-space-infra.yaml
+++ b/address-space-controller/src/main/resources/templates/standard-space-infra.yaml
@@ -278,8 +278,8 @@ objects:
             value: ${TOPIC_FORWARDER_IMAGE}
           - name: IMAGE_PULL_POLICY
             value: ${IMAGE_PULL_POLICY}
-          - name: FS_GROUP_OVERRIDE
-            value: ${FS_GROUP_OVERRIDE}
+          - name: FS_GROUP_FALLBACK_MAP
+            value: ${FS_GROUP_FALLBACK_MAP}
           image: ${STANDARD_CONTROLLER_IMAGE}
           imagePullPolicy: ${IMAGE_PULL_POLICY}
           livenessProbe:
@@ -608,4 +608,4 @@ parameters:
 - description:
   name: CONSOLE_LINK
 - description:
-  name: FS_GROUP_OVERRIDE
+  name: FS_GROUP_FALLBACK_MAP
diff --git a/api-model/src/main/java/io/enmasse/address/model/FsGroupFallback.java b/api-model/src/main/java/io/enmasse/address/model/FsGroupFallback.java
new file mode 100644
index 000000000..f1f4dea17
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/address/model/FsGroupFallback.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019, EnMasse authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ */
+
+package io.enmasse.address.model;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Strings;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class FsGroupFallback {
+
+    private static TypeReference<Map<String, Long>> MAP_STRING_LONG = new TypeReference<>() {
+    };
+
+    private static final Map<String, Long> FS_GROUP_FALLBACK;
+
+    static {
+        final String json = System.getenv().get("FS_GROUP_FALLBACK_MAP");
+        if (Strings.isNullOrEmpty(json)) {
+            FS_GROUP_FALLBACK = new HashMap<>();
+        } else {
+            ObjectMapper objectMapper = new ObjectMapper();
+            try {
+                FS_GROUP_FALLBACK = objectMapper.readValue(json, MAP_STRING_LONG);
+            } catch (JsonProcessingException e) {
+                throw new ExceptionInInitializerError(e);
+            }
+        }
+    }
+
+    public static Long getFsGroupOverride(String component) {
+        return FS_GROUP_FALLBACK.get(component);
+    }
+}
diff --git a/api-model/src/main/java/io/enmasse/address/model/KubeUtil.java b/api-model/src/main/java/io/enmasse/address/model/KubeUtil.java
index d40701b95..15df1786b 100644
--- a/api-model/src/main/java/io/enmasse/address/model/KubeUtil.java
+++ b/api-model/src/main/java/io/enmasse/address/model/KubeUtil.java
@@ -13,16 +13,13 @@ import io.fabric8.kubernetes.api.model.PodSecurityContext;
 import io.fabric8.kubernetes.api.model.PodSpec;
 import io.fabric8.kubernetes.api.model.PodTemplateSpec;
 import io.fabric8.kubernetes.api.model.Probe;
-import io.fabric8.kubernetes.api.model.apps.Deployment;
-import io.fabric8.kubernetes.api.model.apps.StatefulSet;
 
 import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 import java.util.regex.Pattern;
@@ -195,6 +192,10 @@ public final class KubeUtil {
                 actualPodSpec.setTolerations(podSpec.getTolerations());
             }
 
+            if (podSpec.getSecurityContext() != null) {
+                actualPodSpec.setSecurityContext(podSpec.getSecurityContext());
+            }
+
             for (Container desiredContainer : podSpec.getInitContainers()) {
                 for (Container actualContainer : actualPodSpec.getInitContainers()) {
                     if (actualContainer.getName() != null && actualContainer.getName().equals(desiredContainer.getName())) {
@@ -291,23 +292,17 @@ public final class KubeUtil {
         }
     }
 
-    public static void applyFsGroupOverride(List<HasMetadata> items, Long fsGroupOverride) {
-        items.stream().filter(i -> i instanceof Deployment || i instanceof StatefulSet).forEach(
-                i -> {
-                    PodSpec spec;
-                    if (i instanceof StatefulSet) {
-                        spec = ((StatefulSet) i).getSpec().getTemplate().getSpec();
-                    } else  {
-                        spec = ((Deployment) i).getSpec().getTemplate().getSpec();
-                    }
-
-                    PodSecurityContext securityContext = new PodSecurityContext();
-                    if (spec.getSecurityContext() != null) {
-                        securityContext = spec.getSecurityContext();
-                    }
-                    securityContext.setFsGroup(fsGroupOverride);
-                    spec.setSecurityContext(securityContext);
-                }
-        );
+    public static void overrideFsGroup(PodTemplateSpec target, String component, String namespace) {
+        final Long fsGroupOverride = FsGroupFallback.getFsGroupOverride(component);
+        if (Objects.equals(namespace, "openshift-operators") && fsGroupOverride != null) {
+            PodSecurityContext securityContext = target.getSpec().getSecurityContext();
+            if (securityContext == null) {
+                securityContext = new PodSecurityContext();
+            }
+            if (securityContext.getFsGroup() == null) {
+                securityContext.setFsGroup(fsGroupOverride);
+                target.getSpec().setSecurityContext(securityContext);
+            }
+        }
     }
 }
diff --git a/api-model/src/main/java/io/enmasse/admin/model/v1/AuthenticationServiceSpecStandard.java b/api-model/src/main/java/io/enmasse/admin/model/v1/AuthenticationServiceSpecStandard.java
index cf98e2477..1e4623180 100644
--- a/api-model/src/main/java/io/enmasse/admin/model/v1/AuthenticationServiceSpecStandard.java
+++ b/api-model/src/main/java/io/enmasse/admin/model/v1/AuthenticationServiceSpecStandard.java
@@ -7,6 +7,7 @@ package io.enmasse.admin.model.v1;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
 import io.fabric8.kubernetes.api.model.Doneable;
+import io.fabric8.kubernetes.api.model.PodSecurityContext;
 import io.fabric8.kubernetes.api.model.SecretReference;
 import io.sundr.builder.annotations.Buildable;
 import io.sundr.builder.annotations.BuildableReference;
@@ -23,12 +24,13 @@ import java.util.Objects;
         },
         inline = @Inline(type = Doneable.class, prefix = "Doneable", value = "done")
 )
-@JsonPropertyOrder({"credentialsSecret", "certificateSecret", "storage"})
+@JsonPropertyOrder({"credentialsSecret", "certificateSecret", "storage", "securityContext"})
 @JsonInclude(JsonInclude.Include.NON_NULL)
 public class AuthenticationServiceSpecStandard extends AbstractWithAdditionalProperties {
     private SecretReference credentialsSecret;
     private SecretReference certificateSecret;
     private AuthenticationServiceSpecStandardStorage storage;
+    private PodSecurityContext securityContext;
 
     public SecretReference getCredentialsSecret() {
         return credentialsSecret;
@@ -45,12 +47,13 @@ public class AuthenticationServiceSpecStandard extends AbstractWithAdditionalPro
         AuthenticationServiceSpecStandard that = (AuthenticationServiceSpecStandard) o;
         return Objects.equals(credentialsSecret, that.credentialsSecret) &&
                 Objects.equals(certificateSecret, that.certificateSecret) &&
-                Objects.equals(storage, that.storage);
+                Objects.equals(storage, that.storage) &&
+                Objects.equals(securityContext, that.securityContext);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(credentialsSecret, certificateSecret, storage);
+        return Objects.hash(credentialsSecret, certificateSecret, storage, securityContext);
     }
 
     @Override
@@ -59,6 +62,7 @@ public class AuthenticationServiceSpecStandard extends AbstractWithAdditionalPro
                 "credentialsSecret=" + credentialsSecret +
                 ", certificateSecret=" + certificateSecret +
                 ", storage=" + storage +
+                ", securityContext=" + securityContext +
                 '}';
     }
 
@@ -77,4 +81,12 @@ public class AuthenticationServiceSpecStandard extends AbstractWithAdditionalPro
     public void setStorage(AuthenticationServiceSpecStandardStorage storage) {
         this.storage = storage;
     }
+
+    public PodSecurityContext getSecurityContext() {
+        return securityContext;
+    }
+
+    public void setSecurityContext(PodSecurityContext securityContext) {
+        this.securityContext = securityContext;
+    }
 }
diff --git a/api-server/src/main/resources/swagger.json b/api-server/src/main/resources/swagger.json
index 07f15e613..d5418e371 100644
--- a/api-server/src/main/resources/swagger.json
+++ b/api-server/src/main/resources/swagger.json
@@ -310,6 +310,9 @@
                         "priorityClassName": {
                             "type": "string"
                         },
+                        "securityContext": {
+                            "type": "object"
+                        },
                         "tolerations": {
                             "items": {
                                 "type": "object"
diff --git a/pkg/apis/admin/v1beta1/types.go b/pkg/apis/admin/v1beta1/types.go
index 4b52fd18f..85945b2e0 100644
--- a/pkg/apis/admin/v1beta1/types.go
+++ b/pkg/apis/admin/v1beta1/types.go
@@ -58,6 +58,7 @@ type AuthenticationServiceSpecStandard struct {
 	Resources          *corev1.ResourceRequirements                 `json:"resources,omitempty"`
 	Storage            *AuthenticationServiceSpecStandardStorage    `json:"storage,omitempty"`
 	Datasource         *AuthenticationServiceSpecStandardDatasource `json:"datasource,omitempty"`
+	SecurityContext    *corev1.PodSecurityContext                   `json:"securityContext,omitempty"`
 }
 
 type StorageType string
diff --git a/pkg/apis/admin/v1beta1/zz_generated.deepcopy.go b/pkg/apis/admin/v1beta1/zz_generated.deepcopy.go
index baed96542..c4b81b8b1 100644
--- a/pkg/apis/admin/v1beta1/zz_generated.deepcopy.go
+++ b/pkg/apis/admin/v1beta1/zz_generated.deepcopy.go
@@ -233,6 +233,11 @@ func (in *AuthenticationServiceSpecStandard) DeepCopyInto(out *AuthenticationSer
 		*out = new(AuthenticationServiceSpecStandardDatasource)
 		**out = **in
 	}
+	if in.SecurityContext != nil {
+		in, out := &in.SecurityContext, &out.SecurityContext
+		*out = new(v1.PodSecurityContext)
+		(*in).DeepCopyInto(*out)
+	}
 	return
 }
 
diff --git a/pkg/apis/iot/v1alpha1/types_config.go b/pkg/apis/iot/v1alpha1/types_config.go
index 49dce785b..725a1657a 100644
--- a/pkg/apis/iot/v1alpha1/types_config.go
+++ b/pkg/apis/iot/v1alpha1/types_config.go
@@ -163,6 +163,7 @@ type AuthenticationServiceConfig struct {
 type FileBasedDeviceRegistry struct {
 	NumberOfDevicesPerTenant *uint32 `json:"numberOfDevicesPerTenant,omitempty"`
 	CommonServiceConfig      `json:",inline"`
+	SecurityContext          *corev1.PodSecurityContext `json:"securityContext,omitempty"`
 }
 
 type InfinispanDeviceRegistry struct {
diff --git a/pkg/apis/iot/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/iot/v1alpha1/zz_generated.deepcopy.go
index 2c547d47e..4f63a777d 100644
--- a/pkg/apis/iot/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/iot/v1alpha1/zz_generated.deepcopy.go
@@ -530,6 +530,11 @@ func (in *FileBasedDeviceRegistry) DeepCopyInto(out *FileBasedDeviceRegistry) {
 		**out = **in
 	}
 	in.CommonServiceConfig.DeepCopyInto(&out.CommonServiceConfig)
+	if in.SecurityContext != nil {
+		in, out := &in.SecurityContext, &out.SecurityContext
+		*out = new(v1.PodSecurityContext)
+		(*in).DeepCopyInto(*out)
+	}
 	return
 }
 
diff --git a/pkg/controller/address_space_controller/controller.go b/pkg/controller/address_space_controller/controller.go
index 25b0690fc..a3ea85248 100644
--- a/pkg/controller/address_space_controller/controller.go
+++ b/pkg/controller/address_space_controller/controller.go
@@ -158,10 +158,8 @@ func ApplyDeployment(deployment *appsv1.Deployment) error {
 		install.ApplyEnvSimple(container, "ENABLE_EVENT_LOGGER", "true")
 		install.ApplyEnvSimple(container, "TEMPLATE_DIR", "/opt/templates")
 		install.ApplyEnvSimple(container, "RESOURCES_DIR", "/opt")
-
-		value, ok := os.LookupEnv("FS_GROUP_OVERRIDE")
-		if ok {
-			install.ApplyEnvSimple(container, "FS_GROUP_OVERRIDE", value)
+		if value, ok := os.LookupEnv("FS_GROUP_FALLBACK_MAP"); ok {
+			install.ApplyOrRemoveEnvSimple(container, "FS_GROUP_FALLBACK_MAP", value)
 		}
 
 		t := true
diff --git a/pkg/controller/authenticationservice/standard.go b/pkg/controller/authenticationservice/standard.go
index ab69ba6cf..5e0d5ebf5 100644
--- a/pkg/controller/authenticationservice/standard.go
+++ b/pkg/controller/authenticationservice/standard.go
@@ -101,15 +101,7 @@ func applyStandardAuthServiceDeployment(authservice *adminv1beta1.Authentication
 
 	install.ApplyDeploymentDefaults(deployment, "standard-authservice", *authservice.Spec.Standard.DeploymentName)
 
-	err := install.ApplyFsGroupOverride("FS_GROUP_OVERRIDE", deployment)
-	if err != nil {
-		return err
-	}
-
-	err = install.ApplyFsGroupOverride("FS_GROUP_STANDARD_AUTHSERVICE_OVERRIDE", deployment)
-	if err != nil {
-		return err
-	}
+	install.OverrideSecurityContextFsGroup("standard-authservice", authservice.Spec.Standard.SecurityContext, deployment)
 
 	if err := install.ApplyInitContainerWithError(deployment, "keycloak-plugin", func(container *corev1.Container) error {
 		if err := install.ApplyContainerImage(container, "keycloak-plugin", authservice.Spec.Standard.InitImage); err != nil {
diff --git a/pkg/controller/iotconfig/file_device_registry.go b/pkg/controller/iotconfig/file_device_registry.go
index 56be989b3..dfa4b9e1b 100644
--- a/pkg/controller/iotconfig/file_device_registry.go
+++ b/pkg/controller/iotconfig/file_device_registry.go
@@ -73,17 +73,13 @@ func (r *ReconcileIoTConfig) reconcileFileDeviceRegistryDeployment(config *iotv1
 
 	applyDefaultDeploymentConfig(deployment, config.Spec.ServicesConfig.DeviceRegistry.ServiceConfig, nil)
 
-	err := install.ApplyFsGroupOverride("FS_GROUP_OVERRIDE", deployment)
-
-	if err != nil {
-		return err
-	}
+	install.OverrideSecurityContextFsGroup("file-device-registry", config.Spec.ServicesConfig.DeviceRegistry.File.SecurityContext, deployment)
 
 	// the file based device registry must use the re-create strategy
 	// this is necessary to detach the volume first
 	deployment.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
 
-	err = install.ApplyContainerWithError(deployment, "device-registry", func(container *corev1.Container) error {
+	err := install.ApplyContainerWithError(deployment, "device-registry", func(container *corev1.Container) error {
 
 		if err := install.SetContainerImage(container, "iot-device-registry-file", config); err != nil {
 			return err
diff --git a/pkg/util/fsgroupfallback.go b/pkg/util/fsgroupfallback.go
new file mode 100644
index 000000000..381f808c6
--- /dev/null
+++ b/pkg/util/fsgroupfallback.go
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019, EnMasse authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ *
+ */
+
+package util
+
+import (
+	"encoding/json"
+)
+
+var fsGroupFallbackMap map[string]int64
+
+func lazyLoadFsGroupFallbackMap() {
+	if fsGroupFallbackMap != nil {
+		return
+	}
+
+	jsonStr := GetEnvOrDefault("FS_GROUP_FALLBACK_MAP", "{}")
+
+	var data = make(map[string]int64)
+	if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
+		panic(err)
+	}
+	fsGroupFallbackMap = data
+}
+
+func GetFsGroupOverride(component string) *int64 {
+	lazyLoadFsGroupFallbackMap()
+	namespace := GetEnvOrDefault("NAMESPACE", "enmasse-infra")
+
+	if i, ok := fsGroupFallbackMap[component]; ok && namespace == "openshift-operators" {
+		return &i
+	} else {
+		return nil
+	}
+}
diff --git a/pkg/util/install/utils.go b/pkg/util/install/utils.go
index cfdf28e1c..a2dd0f624 100644
--- a/pkg/util/install/utils.go
+++ b/pkg/util/install/utils.go
@@ -7,8 +7,6 @@ package install
 
 import (
 	"fmt"
-	"strconv"
-
 	"k8s.io/apimachinery/pkg/runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
 
@@ -494,22 +492,16 @@ func ApplyNodeAffinity(template *corev1.PodTemplateSpec, matchKey string) {
 	}
 }
 
-// This is a workaround for a problem that may manifest when EnMasse is deployed into OLM cluster-wide
-// under certain configurations. If https://github.com/operator-framework/operator-lifecycle-manager/issues/927
-// is resolved, this workaround can be removed.
-func ApplyFsGroupOverride(envVar string, deployment *appsv1.Deployment) error {
-	err := util.ApplyEnv(envVar, func(name string, value string, ok bool) error {
-		if ok {
-			fsGroupOverride, err := strconv.ParseInt(value, 10, 0)
-			if deployment.Spec.Template.Spec.SecurityContext == nil {
-				deployment.Spec.Template.Spec.SecurityContext = &corev1.PodSecurityContext{}
-			}
-			deployment.Spec.Template.Spec.SecurityContext.FSGroup = &fsGroupOverride
-			return err
-		}
-		return nil
-	})
-	return err
+func OverrideSecurityContextFsGroup(componentName string, securityContext *corev1.PodSecurityContext, target *appsv1.Deployment) {
+	override := util.GetFsGroupOverride(componentName)
+	if securityContext == nil {
+		target.Spec.Template.Spec.SecurityContext = &corev1.PodSecurityContext{}
+	} else {
+		target.Spec.Template.Spec.SecurityContext = securityContext
+	}
+	if target.Spec.Template.Spec.SecurityContext.FSGroup == nil {
+		target.Spec.Template.Spec.SecurityContext.FSGroup = override
+	}
 }
 
 // Ensure that an owner is set
diff --git a/standard-controller/src/main/java/io/enmasse/controller/standard/Kubernetes.java b/standard-controller/src/main/java/io/enmasse/controller/standard/Kubernetes.java
index 64abfe0a9..eb3ac361a 100644
--- a/standard-controller/src/main/java/io/enmasse/controller/standard/Kubernetes.java
+++ b/standard-controller/src/main/java/io/enmasse/controller/standard/Kubernetes.java
@@ -60,4 +60,6 @@ public interface Kubernetes {
     void scaleStatefulSet(String name, int numReplicas);
 
     List<Pod> listBrokers(String clusterId);
+
+    String getNamespace();
 }
diff --git a/standard-controller/src/main/java/io/enmasse/controller/standard/KubernetesHelper.java b/standard-controller/src/main/java/io/enmasse/controller/standard/KubernetesHelper.java
index 5417399fb..b7c66d75f 100644
--- a/standard-controller/src/main/java/io/enmasse/controller/standard/KubernetesHelper.java
+++ b/standard-controller/src/main/java/io/enmasse/controller/standard/KubernetesHelper.java
@@ -225,4 +225,8 @@ public class KubernetesHelper implements Kubernetes {
         client.apps().deployments().withName(deployment.getMetadata().getName()).scale(numReplicas);
     }
 
+    @Override
+    public String getNamespace() {
+        return client.getNamespace();
+    }
 }
diff --git a/standard-controller/src/main/java/io/enmasse/controller/standard/TemplateBrokerSetGenerator.java b/standard-controller/src/main/java/io/enmasse/controller/standard/TemplateBrokerSetGenerator.java
index 05641dfc7..324919085 100644
--- a/standard-controller/src/main/java/io/enmasse/controller/standard/TemplateBrokerSetGenerator.java
+++ b/standard-controller/src/main/java/io/enmasse/controller/standard/TemplateBrokerSetGenerator.java
@@ -17,23 +17,21 @@ import java.util.*;
 import java.util.concurrent.TimeUnit;
 
 import static io.enmasse.address.model.KubeUtil.applyPodTemplate;
+import static io.enmasse.address.model.KubeUtil.overrideFsGroup;
 
 /**
  * Generates sets of brokers using Openshift templates.
  */
 public class TemplateBrokerSetGenerator implements BrokerSetGenerator {
-    private static final String FS_GROUP_OVERRIDE = "FS_GROUP_OVERRIDE";
     private static final ObjectMapper mapper = new ObjectMapper();
     private final Kubernetes kubernetes;
     private final StandardControllerOptions options;
     private final Map<String, String> env;
-    private final Long fsGroupOverride;
 
     public TemplateBrokerSetGenerator(Kubernetes kubernetes, StandardControllerOptions options, Map<String, String> env) {
         this.kubernetes = kubernetes;
         this.options = options;
         this.env = env;
-        this.fsGroupOverride = getFsGroupOverride();
     }
 
     private boolean isShardedTopic(AddressPlan addressPlan) {
@@ -154,10 +152,7 @@ public class TemplateBrokerSetGenerator implements BrokerSetGenerator {
                     PodTemplateSpec actualPodTemplate = set.getSpec().getTemplate();
                     applyPodTemplate(actualPodTemplate, podTemplate);
                 }
-
-                if (fsGroupOverride != null) {
-                    KubeUtil.applyFsGroupOverride(Collections.singletonList(item), fsGroupOverride);
-                }
+                overrideFsGroup(set.getSpec().getTemplate(), "broker",  kubernetes.getNamespace());
             } else if (item instanceof Deployment) {
                 Deployment deployment = (Deployment) item;
                 deployment.getSpec().setReplicas(numReplicas);
@@ -178,16 +173,4 @@ public class TemplateBrokerSetGenerator implements BrokerSetGenerator {
             parameters.put(key, env.get(key));
         }
     }
-
-    private Long getFsGroupOverride() {
-        Long fsGroupOverride = null;
-        if (env.containsKey(FS_GROUP_OVERRIDE)) {
-            try {
-                fsGroupOverride = Long.parseLong(env.get(FS_GROUP_OVERRIDE));
-            } catch (NumberFormatException ignore) {
-            }
-        }
-        return fsGroupOverride;
-    }
-
 }
diff --git a/templates/crds/authenticationservices.crd.yaml b/templates/crds/authenticationservices.crd.yaml
index bbad6b3a9..5cc222eba 100644
--- a/templates/crds/authenticationservices.crd.yaml
+++ b/templates/crds/authenticationservices.crd.yaml
@@ -155,6 +155,8 @@ spec:
                           type: string
                         namespace:
                           type: string
+                securityContext:
+                  type: object
             external:
               type: object
               required:
diff --git a/templates/crds/brokeredinfraconfigs.crd.yaml b/templates/crds/brokeredinfraconfigs.crd.yaml
index 1419fa8fc..e51abaa65 100644
--- a/templates/crds/brokeredinfraconfigs.crd.yaml
+++ b/templates/crds/brokeredinfraconfigs.crd.yaml
@@ -57,6 +57,8 @@ spec:
                             type: object
                         priorityClassName:
                           type: string
+                        securityContext:
+                          type: object
                         containers:
                           type: array
                           items:
@@ -89,6 +91,8 @@ spec:
                           type: array
                         priorityClassName:
                           type: string
+                        securityContext:
+                          type: object
                         resources:
                           type: object
                 resources:
diff --git a/templates/crds/standardinfraconfigs.crd.yaml b/templates/crds/standardinfraconfigs.crd.yaml
index d91ecdc0e..79a7eb016 100644
--- a/templates/crds/standardinfraconfigs.crd.yaml
+++ b/templates/crds/standardinfraconfigs.crd.yaml
@@ -62,6 +62,8 @@ spec:
                             type: object
                         priorityClassName:
                           type: string
+                        securityContext:
+                          type: object
                         containers:
                           type: array
                           items:
@@ -89,6 +91,8 @@ spec:
                           type: array
                         priorityClassName:
                           type: string
+                        securityContext:
+                          type: object
                         resources:
                           type: object
                 resources:
@@ -133,6 +137,8 @@ spec:
                           type: array
                         priorityClassName:
                           type: string
+                        securityContext:
+                          type: object
                         resources:
                           type: object
                 resources:


Test patch:
diff --git a/api-model/src/test/java/io/enmasse/address/model/v1/address/KubeUtilTest.java b/api-model/src/test/java/io/enmasse/address/model/v1/address/KubeUtilTest.java
index 16751717b..c55c51d3b 100644
--- a/api-model/src/test/java/io/enmasse/address/model/v1/address/KubeUtilTest.java
+++ b/api-model/src/test/java/io/enmasse/address/model/v1/address/KubeUtilTest.java
@@ -105,6 +105,31 @@ public class KubeUtilTest {
         assertThat(fooContainer.getResources(), equalTo(resources));
     }
 
+    @Test
+    public void appliesSecurityContext() {
+
+        PodTemplateSpec actual = new PodTemplateSpecBuilder()
+                .withNewSpec()
+                .endSpec()
+                .build();
+
+        final long runAsGroup = 1000L;
+        final long fsGroup = 1234L;
+        PodTemplateSpec desired = new PodTemplateSpecBuilder()
+                .withNewSpec()
+                .withNewSecurityContext()
+                .withRunAsGroup(runAsGroup)
+                .withFsGroup(fsGroup)
+                .endSecurityContext()
+                .endSpec()
+                .build();
+
+        KubeUtil.applyPodTemplate(actual, desired);
+
+        assertThat(actual.getSpec().getSecurityContext().getFsGroup(), equalTo(fsGroup));
+        assertThat(actual.getSpec().getSecurityContext().getRunAsGroup(), equalTo(runAsGroup));
+    }
+
     @Test
     public void appliesContainerEnvVarToPodTemplate() {
         Container actualContainer = new ContainerBuilder()
diff --git a/olm-manifest/src/main/resources/latest/enmasse.clusterserviceversion.yaml b/olm-manifest/src/main/resources/latest/enmasse.clusterserviceversion.yaml
index 05b784601..22d0430e3 100644
--- a/olm-manifest/src/main/resources/latest/enmasse.clusterserviceversion.yaml
+++ b/olm-manifest/src/main/resources/latest/enmasse.clusterserviceversion.yaml
@@ -697,8 +697,8 @@ spec:
                   value: "${env.CONSOLE_PROXY_KUBERNETES_IMAGE}"
                 - name: CONSOLE_HTTPD_IMAGE
                   value: "${env.CONSOLE_HTTPD_IMAGE}"
-                - name: FS_GROUP_STANDARD_AUTHSERVICE_OVERRIDE
-                  value: "1000"
+                - name: "FS_GROUP_FALLBACK_MAP"
+                  value: "{\"standard-authservice\": 1000}"
       - name: user-api-server
         spec:
           replicas: 1