From 8fb2555ba257cd744c999da87a10782898b9b46c Mon Sep 17 00:00:00 2001
From: Lucas Caparelli <lucas.caparelli112@gmail.com>
Date: Sun, 18 Oct 2020 19:17:41 -0300
Subject: [PATCH 1/3] Generate SCC-related resources when on OCP

Signed-off-by: Lucas Caparelli <lucas.caparelli112@gmail.com>
---
 .../nexus-operator.clusterserviceversion.yaml | 19 +++++
 config/rbac/role.yaml                         | 19 +++++
 .../nexus/resource/deployment/manager.go      | 29 +++-----
 .../nexus/resource/deployment/manager_test.go |  6 +-
 .../nexus/resource/networking/manager.go      | 58 ++++++---------
 .../nexus/resource/networking/manager_test.go | 14 +---
 .../nexus/resource/persistence/manager.go     | 27 +++----
 .../resource/persistence/manager_test.go      |  6 +-
 controllers/nexus/resource/resources.go       |  8 ++-
 .../nexus/resource/security/cluster_role.go   | 41 +++++++++++
 .../nexus/resource/security/manager.go        | 71 +++++++++++++------
 .../nexus/resource/security/manager_test.go   | 30 ++------
 .../nexus/resource/security/role_binding.go   | 42 +++++++++++
 controllers/nexus/resource/security/scc.go    | 50 +++++++++++++
 controllers/nexus_controller.go               |  2 +
 nexus-operator.yaml                           | 19 +++++
 pkg/framework/fetcher.go                      | 17 +++++
 pkg/framework/kinds.go                        | 17 +++--
 18 files changed, 328 insertions(+), 147 deletions(-)
 create mode 100644 controllers/nexus/resource/security/cluster_role.go
 create mode 100644 controllers/nexus/resource/security/role_binding.go
 create mode 100644 controllers/nexus/resource/security/scc.go

diff --git a/bundle/manifests/nexus-operator.clusterserviceversion.yaml b/bundle/manifests/nexus-operator.clusterserviceversion.yaml
index b98ff23e..b4d13ef7 100644
--- a/bundle/manifests/nexus-operator.clusterserviceversion.yaml
+++ b/bundle/manifests/nexus-operator.clusterserviceversion.yaml
@@ -167,6 +167,16 @@ spec:
           - patch
           - update
           - watch
+        - apiGroups:
+          - rbac.authorization.k8s.io
+          resources:
+          - clusterrole
+          - rolebinding
+          verbs:
+          - create
+          - get
+          - update
+          - watch
         - apiGroups:
           - route.openshift.io
           resources:
@@ -179,6 +189,15 @@ spec:
           - patch
           - update
           - watch
+        - apiGroups:
+          - security.openshift.io
+          resources:
+          - scc
+          verbs:
+          - create
+          - get
+          - update
+          - watch
         - apiGroups:
           - authentication.k8s.io
           resources:
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index 92cb9563..12445393 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -98,6 +98,16 @@ rules:
   - patch
   - update
   - watch
+- apiGroups:
+  - rbac.authorization.k8s.io
+  resources:
+  - clusterrole
+  - rolebinding
+  verbs:
+  - create
+  - get
+  - update
+  - watch
 - apiGroups:
   - route.openshift.io
   resources:
@@ -110,3 +120,12 @@ rules:
   - patch
   - update
   - watch
+- apiGroups:
+  - security.openshift.io
+  resources:
+  - scc
+  verbs:
+  - create
+  - get
+  - update
+  - watch
diff --git a/controllers/nexus/resource/deployment/manager.go b/controllers/nexus/resource/deployment/manager.go
index 5051501f..a1addfa2 100644
--- a/controllers/nexus/resource/deployment/manager.go
+++ b/controllers/nexus/resource/deployment/manager.go
@@ -15,7 +15,6 @@
 package deployment
 
 import (
-	"fmt"
 	"reflect"
 	"strings"
 
@@ -23,7 +22,6 @@ import (
 	"github.com/RHsyseng/operator-utils/pkg/resource/compare"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/api/errors"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/m88i/nexus-operator/api/v1alpha1"
@@ -31,17 +29,13 @@ import (
 	"github.com/m88i/nexus-operator/pkg/logger"
 )
 
-var managedObjectsRef = map[string]resource.KubernetesResource{
-	framework.DeploymentKind: &appsv1.Deployment{},
-	framework.ServiceKind:    &corev1.Service{},
-}
-
 // Manager is responsible for creating deployment-related resources, fetching deployed ones and comparing them
 // Use with zero values will result in a panic. Use the NewManager function to get a properly initialized manager
 type Manager struct {
-	nexus  *v1alpha1.Nexus
-	client client.Client
-	log    logger.Logger
+	nexus             *v1alpha1.Nexus
+	client            client.Client
+	log               logger.Logger
+	managedObjectsRef map[string]resource.KubernetesResource
 }
 
 // NewManager creates a deployment resources manager
@@ -51,6 +45,11 @@ func NewManager(nexus *v1alpha1.Nexus, client client.Client) *Manager {
 		nexus:  nexus,
 		client: client,
 		log:    logger.GetLoggerWithResource("deployment_manager", nexus),
+
+		managedObjectsRef: map[string]resource.KubernetesResource{
+			framework.DeploymentKind: &appsv1.Deployment{},
+			framework.ServiceKind:    &corev1.Service{},
+		},
 	}
 }
 
@@ -63,15 +62,7 @@ func (m *Manager) GetRequiredResources() ([]resource.KubernetesResource, error)
 
 // GetDeployedResources returns the deployment-related resources deployed on the cluster
 func (m *Manager) GetDeployedResources() ([]resource.KubernetesResource, error) {
-	var resources []resource.KubernetesResource
-	for resType, resRef := range managedObjectsRef {
-		if err := framework.Fetch(m.client, framework.Key(m.nexus), resRef, resType); err == nil {
-			resources = append(resources, resRef)
-		} else if !errors.IsNotFound(err) {
-			return nil, fmt.Errorf("could not fetch %s (%s/%s): %v", resType, m.nexus.Namespace, m.nexus.Name, err)
-		}
-	}
-	return resources, nil
+	return framework.FetchDeployedResources(m.managedObjectsRef, m.nexus, m.client)
 }
 
 // GetCustomComparator returns the custom comp function used to compare a deployment-related resource
diff --git a/controllers/nexus/resource/deployment/manager_test.go b/controllers/nexus/resource/deployment/manager_test.go
index eaff6a14..441931cc 100644
--- a/controllers/nexus/resource/deployment/manager_test.go
+++ b/controllers/nexus/resource/deployment/manager_test.go
@@ -79,10 +79,8 @@ func TestManager_GetRequiredResources(t *testing.T) {
 func TestManager_GetDeployedResources(t *testing.T) {
 	// first no deployed resources
 	fakeClient := test.NewFakeClientBuilder().Build()
-	mgr := &Manager{
-		nexus:  allDefaultsCommunityNexus,
-		client: fakeClient,
-	}
+	mgr := NewManager(allDefaultsCommunityNexus, fakeClient)
+
 	resources, err := mgr.GetDeployedResources()
 	assert.Nil(t, resources)
 	assert.Len(t, resources, 0)
diff --git a/controllers/nexus/resource/networking/manager.go b/controllers/nexus/resource/networking/manager.go
index 288202a2..e4a8461c 100644
--- a/controllers/nexus/resource/networking/manager.go
+++ b/controllers/nexus/resource/networking/manager.go
@@ -22,7 +22,6 @@ import (
 	"github.com/RHsyseng/operator-utils/pkg/resource/compare"
 	routev1 "github.com/openshift/api/route/v1"
 	networkingv1beta1 "k8s.io/api/networking/v1beta1"
-	"k8s.io/apimachinery/pkg/api/errors"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/m88i/nexus-operator/api/v1alpha1"
@@ -32,7 +31,6 @@ import (
 )
 
 const (
-	discOCPFailureFormat = "unable to determine if cluster is Openshift: %v"
 	discFailureFormat    = "unable to determine if %s are available: %v" // resource type, error
 	resUnavailableFormat = "%s are not available in this cluster"        // resource type
 )
@@ -40,38 +38,43 @@ const (
 // Manager is responsible for creating networking resources, fetching deployed ones and comparing them
 // Use with zero values will result in a panic. Use the NewManager function to get a properly initialized manager
 type Manager struct {
-	nexus                                 *v1alpha1.Nexus
-	client                                client.Client
-	log                                   logger.Logger
-	routeAvailable, ingressAvailable, ocp bool
+	nexus             *v1alpha1.Nexus
+	client            client.Client
+	log               logger.Logger
+	managedObjectsRef map[string]resource.KubernetesResource
+
+	routeAvailable, ingressAvailable bool
 }
 
 // NewManager creates a networking resources manager
 // It is expected that the Nexus has been previously validated.
 func NewManager(nexus *v1alpha1.Nexus, client client.Client) (*Manager, error) {
+	mgr := &Manager{
+		nexus:             nexus,
+		client:            client,
+		log:               logger.GetLoggerWithResource("networking_manager", nexus),
+		managedObjectsRef: make(map[string]resource.KubernetesResource),
+	}
+
 	routeAvailable, err := discovery.IsRouteAvailable()
 	if err != nil {
 		return nil, fmt.Errorf(discFailureFormat, "routes", err)
 	}
+	if routeAvailable {
+		mgr.routeAvailable = true
+		mgr.managedObjectsRef[framework.RouteKind] = &routev1.Route{}
+	}
 
 	ingressAvailable, err := discovery.IsIngressAvailable()
 	if err != nil {
 		return nil, fmt.Errorf(discFailureFormat, "ingresses", err)
 	}
-
-	ocp, err := discovery.IsOpenShift()
-	if err != nil {
-		return nil, fmt.Errorf(discOCPFailureFormat, err)
+	if ingressAvailable {
+		mgr.ingressAvailable = true
+		mgr.managedObjectsRef[framework.IngressKind] = &networkingv1beta1.Ingress{}
 	}
 
-	return &Manager{
-		nexus:            nexus,
-		client:           client,
-		routeAvailable:   routeAvailable,
-		ingressAvailable: ingressAvailable,
-		ocp:              ocp,
-		log:              logger.GetLoggerWithResource("networking_manager", nexus),
-	}, nil
+	return mgr, nil
 }
 
 func (m *Manager) IngressAvailable() bool {
@@ -129,24 +132,7 @@ func (m *Manager) createIngress() *networkingv1beta1.Ingress {
 
 // GetDeployedResources returns the networking resources deployed on the cluster
 func (m *Manager) GetDeployedResources() ([]resource.KubernetesResource, error) {
-	var resources []resource.KubernetesResource
-	if m.routeAvailable {
-		route := &routev1.Route{}
-		if err := framework.Fetch(m.client, framework.Key(m.nexus), route, framework.RouteKind); err == nil {
-			resources = append(resources, route)
-		} else if !errors.IsNotFound(err) {
-			return nil, fmt.Errorf("could not fetch %s (%s/%s): %v", framework.RouteKind, m.nexus.Namespace, m.nexus.Name, err)
-		}
-	}
-	if m.ingressAvailable {
-		ingress := &networkingv1beta1.Ingress{}
-		if err := framework.Fetch(m.client, framework.Key(m.nexus), ingress, framework.IngressKind); err == nil {
-			resources = append(resources, ingress)
-		} else if !errors.IsNotFound(err) {
-			return nil, fmt.Errorf("could not fetch %s (%s/%s): %v", framework.IngressKind, m.nexus.Namespace, m.nexus.Name, err)
-		}
-	}
-	return resources, nil
+	return framework.FetchDeployedResources(m.managedObjectsRef, m.nexus, m.client)
 }
 
 // GetCustomComparator returns the custom comp function used to compare a networking resource.
diff --git a/controllers/nexus/resource/networking/manager_test.go b/controllers/nexus/resource/networking/manager_test.go
index b0542515..e9ef8bda 100644
--- a/controllers/nexus/resource/networking/manager_test.go
+++ b/controllers/nexus/resource/networking/manager_test.go
@@ -79,7 +79,6 @@ func TestNewManager(t *testing.T) {
 				client:           test.NewFakeClientBuilder().WithIngress().Build(),
 				routeAvailable:   false,
 				ingressAvailable: true,
-				ocp:              false,
 			},
 			k8sClientWithIngress,
 		},
@@ -90,7 +89,6 @@ func TestNewManager(t *testing.T) {
 				client:           test.NewFakeClientBuilder().Build(),
 				routeAvailable:   false,
 				ingressAvailable: false,
-				ocp:              false,
 			},
 			k8sClient,
 		},
@@ -101,7 +99,6 @@ func TestNewManager(t *testing.T) {
 				client:           test.NewFakeClientBuilder().OnOpenshift().Build(),
 				routeAvailable:   true,
 				ingressAvailable: false,
-				ocp:              true,
 			},
 			ocpClient,
 		},
@@ -115,7 +112,6 @@ func TestNewManager(t *testing.T) {
 		assert.NotNil(t, got.nexus)
 		assert.Equal(t, tt.want.routeAvailable, got.routeAvailable)
 		assert.Equal(t, tt.want.ingressAvailable, got.ingressAvailable)
-		assert.Equal(t, tt.want.ocp, got.ocp)
 	}
 
 	// simulate discovery 500 response, expect error
@@ -147,7 +143,6 @@ func TestManager_GetRequiredResources(t *testing.T) {
 		client:         test.NewFakeClientBuilder().OnOpenshift().Build(),
 		log:            logger.GetLoggerWithResource("test", routeNexus),
 		routeAvailable: true,
-		ocp:            true,
 	}
 	resources, err = mgr.GetRequiredResources()
 	assert.Nil(t, err)
@@ -216,13 +211,8 @@ func TestManager_createIngress(t *testing.T) {
 func TestManager_GetDeployedResources(t *testing.T) {
 	// first with no deployed resources
 	fakeClient := test.NewFakeClientBuilder().WithIngress().OnOpenshift().Build()
-	mgr := &Manager{
-		nexus:            nodePortNexus,
-		client:           fakeClient,
-		ingressAvailable: true,
-		routeAvailable:   true,
-		ocp:              true,
-	}
+	mgr, _ := NewManager(nodePortNexus, fakeClient, fakeClient)
+
 	resources, err := mgr.GetDeployedResources()
 	assert.Nil(t, resources)
 	assert.Len(t, resources, 0)
diff --git a/controllers/nexus/resource/persistence/manager.go b/controllers/nexus/resource/persistence/manager.go
index 64d7d5a3..49fac32f 100644
--- a/controllers/nexus/resource/persistence/manager.go
+++ b/controllers/nexus/resource/persistence/manager.go
@@ -15,12 +15,10 @@
 package persistence
 
 import (
-	"fmt"
 	"reflect"
 
 	"github.com/RHsyseng/operator-utils/pkg/resource"
 	corev1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/api/errors"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/m88i/nexus-operator/api/v1alpha1"
@@ -28,16 +26,13 @@ import (
 	"github.com/m88i/nexus-operator/pkg/logger"
 )
 
-var managedObjectsRef = map[string]resource.KubernetesResource{
-	framework.PVCKind: &corev1.PersistentVolumeClaim{},
-}
-
 // Manager is responsible for creating persistence resources, fetching deployed ones and comparing them
 // Use with zero values will result in a panic. Use the NewManager function to get a properly initialized manager
 type Manager struct {
-	nexus  *v1alpha1.Nexus
-	client client.Client
-	log    logger.Logger
+	nexus             *v1alpha1.Nexus
+	client            client.Client
+	log               logger.Logger
+	managedObjectsRef map[string]resource.KubernetesResource
 }
 
 // NewManager creates a persistence resources manager
@@ -47,6 +42,10 @@ func NewManager(nexus *v1alpha1.Nexus, client client.Client) *Manager {
 		nexus:  nexus,
 		client: client,
 		log:    logger.GetLoggerWithResource("persistence_manager", nexus),
+
+		managedObjectsRef: map[string]resource.KubernetesResource{
+			framework.PVCKind: &corev1.PersistentVolumeClaim{},
+		},
 	}
 }
 
@@ -66,15 +65,7 @@ func (m *Manager) GetRequiredResources() ([]resource.KubernetesResource, error)
 
 // GetDeployedResources returns the persistence resources deployed on the cluster
 func (m *Manager) GetDeployedResources() ([]resource.KubernetesResource, error) {
-	var resources []resource.KubernetesResource
-	for resType, resRef := range managedObjectsRef {
-		if err := framework.Fetch(m.client, framework.Key(m.nexus), resRef, resType); err == nil {
-			resources = append(resources, resRef)
-		} else if !errors.IsNotFound(err) {
-			return nil, fmt.Errorf("could not fetch %s (%s/%s): %v", resType, m.nexus.Namespace, m.nexus.Name, err)
-		}
-	}
-	return resources, nil
+	return framework.FetchDeployedResources(m.managedObjectsRef, m.nexus, m.client)
 }
 
 // GetCustomComparator returns the custom comp function used to compare a persistence resource.
diff --git a/controllers/nexus/resource/persistence/manager_test.go b/controllers/nexus/resource/persistence/manager_test.go
index 9c333e06..8f9075e4 100644
--- a/controllers/nexus/resource/persistence/manager_test.go
+++ b/controllers/nexus/resource/persistence/manager_test.go
@@ -75,10 +75,8 @@ func TestManager_GetRequiredResources(t *testing.T) {
 func TestManager_GetDeployedResources(t *testing.T) {
 	// first with no deployed resources
 	fakeClient := test.NewFakeClientBuilder().Build()
-	mgr := &Manager{
-		nexus:  baseNexus,
-		client: fakeClient,
-	}
+	mgr := NewManager(baseNexus, fakeClient)
+
 	resources, err := mgr.GetDeployedResources()
 	assert.Nil(t, resources)
 	assert.Len(t, resources, 0)
diff --git a/controllers/nexus/resource/resources.go b/controllers/nexus/resource/resources.go
index cc39dcc9..bfd6cd31 100644
--- a/controllers/nexus/resource/resources.go
+++ b/controllers/nexus/resource/resources.go
@@ -49,6 +49,12 @@ func NewSupervisor(client client.Client) Supervisor {
 // InitManagers initializes the managers responsible for the resources life cycle
 func (s *supervisor) InitManagers(nexus *v1alpha1.Nexus) error {
 	s.log = logger.GetLoggerWithResource("resource_supervisor", nexus)
+
+	securityManager, err := security.NewManager(nexus, s.client)
+	if err != nil {
+		return fmt.Errorf("unable to create security manager: %v", err)
+	}
+
 	networkManager, err := networking.NewManager(nexus, s.client)
 	if err != nil {
 		return fmt.Errorf("unable to create networking manager: %v", err)
@@ -57,7 +63,7 @@ func (s *supervisor) InitManagers(nexus *v1alpha1.Nexus) error {
 	s.managers = []Manager{
 		deployment.NewManager(nexus, s.client),
 		persistence.NewManager(nexus, s.client),
-		security.NewManager(nexus, s.client),
+		securityManager,
 		networkManager,
 	}
 	return nil
diff --git a/controllers/nexus/resource/security/cluster_role.go b/controllers/nexus/resource/security/cluster_role.go
new file mode 100644
index 00000000..ded5192a
--- /dev/null
+++ b/controllers/nexus/resource/security/cluster_role.go
@@ -0,0 +1,41 @@
+// Copyright 2020 Nexus Operator and/or its authors
+//
+// 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 security
+
+import (
+	secv1 "github.com/openshift/api/security/v1"
+	rbacv1 "k8s.io/api/rbac/v1"
+	controllerruntime "sigs.k8s.io/controller-runtime"
+)
+
+const (
+	verbUse         = "use"
+	sccResourceName = "securitycontextconstraints"
+	clusterRoleName = "nexus-community"
+)
+
+func defaultClusterRole() *rbacv1.ClusterRole {
+	return &rbacv1.ClusterRole{
+		ObjectMeta: controllerruntime.ObjectMeta{Name: clusterRoleName},
+		Rules: []rbacv1.PolicyRule{
+			{
+				APIGroups:     []string{secv1.GroupName},
+				ResourceNames: []string{sccName},
+				Resources:     []string{sccResourceName},
+				Verbs:         []string{verbUse},
+			},
+		},
+	}
+}
diff --git a/controllers/nexus/resource/security/manager.go b/controllers/nexus/resource/security/manager.go
index bb71f931..e0643beb 100644
--- a/controllers/nexus/resource/security/manager.go
+++ b/controllers/nexus/resource/security/manager.go
@@ -17,59 +17,88 @@ package security
 import (
 	"fmt"
 	"reflect"
+	"strings"
 
 	"github.com/RHsyseng/operator-utils/pkg/resource"
+	secv1 "github.com/openshift/api/security/v1"
 	core "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/api/errors"
+	rbacv1 "k8s.io/api/rbac/v1"
+	"k8s.io/client-go/discovery"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/m88i/nexus-operator/api/v1alpha1"
+	"github.com/m88i/nexus-operator/controllers/nexus/resource/validation"
+	"github.com/m88i/nexus-operator/pkg/cluster/openshift"
 	"github.com/m88i/nexus-operator/pkg/framework"
 	"github.com/m88i/nexus-operator/pkg/logger"
 )
 
-var managedObjectsRef = map[string]resource.KubernetesResource{
-	framework.SecretKind:     &core.Secret{},
-	framework.SvcAccountKind: &core.ServiceAccount{},
-}
-
 // Manager is responsible for creating security resources, fetching deployed ones and comparing them
 // Use with zero values will result in a panic. Use the NewManager function to get a properly initialized manager
 type Manager struct {
-	nexus  *v1alpha1.Nexus
-	client client.Client
-	log    logger.Logger
+	nexus             *v1alpha1.Nexus
+	client            client.Client
+	log               logger.Logger
+	isOCP             bool
+	managedObjectsRef map[string]resource.KubernetesResource
 }
 
 // NewManager creates a security resources Manager
-func NewManager(nexus *v1alpha1.Nexus, client client.Client) *Manager {
-	return &Manager{
+func NewManager(nexus *v1alpha1.Nexus, client client.Client, disc discovery.DiscoveryInterface) (*Manager, error) {
+	mgr := &Manager{
 		nexus:  nexus,
 		client: client,
 		log:    logger.GetLoggerWithResource("security_manager", nexus),
+
+		managedObjectsRef: map[string]resource.KubernetesResource{
+			framework.SecretKind:     &core.Secret{},
+			framework.SvcAccountKind: &core.ServiceAccount{},
+		},
+	}
+
+	isOCP, err := openshift.IsOpenShift(disc)
+	if err != nil {
+		return nil, fmt.Errorf("unable to determine if on Openshift: %v", err)
+	}
+	if isOCP {
+		mgr.isOCP = true
+		mgr.managedObjectsRef[framework.SCCKind] = &secv1.SecurityContextConstraints{}
+		mgr.managedObjectsRef[framework.RoleBindingKind] = &rbacv1.RoleBinding{}
+		mgr.managedObjectsRef[framework.ClusterRoleKind] = &rbacv1.ClusterRole{}
 	}
+
+	return mgr, nil
 }
 
 // GetRequiredResources returns the resources initialized by the Manager
 func (m *Manager) GetRequiredResources() ([]resource.KubernetesResource, error) {
 	m.log.Debug("Generating required resource", "kind", framework.SvcAccountKind)
 	m.log.Debug("Generating required resource", "kind", framework.SecretKind)
-	return []resource.KubernetesResource{defaultServiceAccount(m.nexus), defaultSecret(m.nexus)}, nil
-}
+	resources := []resource.KubernetesResource{defaultServiceAccount(m.nexus), defaultSecret(m.nexus)}
 
-// GetDeployedResources returns the security resources deployed on the cluster
-func (m *Manager) GetDeployedResources() ([]resource.KubernetesResource, error) {
-	var resources []resource.KubernetesResource
-	for resType, resRef := range managedObjectsRef {
-		if err := framework.Fetch(m.client, framework.Key(m.nexus), resRef, resType); err == nil {
-			resources = append(resources, resRef)
-		} else if !errors.IsNotFound(err) {
-			return nil, fmt.Errorf("could not fetch %s (%s/%s): %v", resType, m.nexus.Namespace, m.nexus.Name, err)
+	if m.isOCP {
+		// the SCC and the ClusterRole are cluster-scoped, but still part of the desired state
+		// so we should ensure they're properly configured on each reconciliation
+		m.log.Debug("Generating required resource", "kind", framework.SCCKind)
+		resources = append(resources, defaultSCC())
+		m.log.Debug("Generating required resource", "kind", framework.ClusterRoleKind)
+		resources = append(resources, defaultClusterRole())
+
+		// we only want to bind the Service Account to the ClusterRole if using the community image
+		if m.nexus.Spec.Image == strings.Split(validation.NexusCommunityImage, ":")[0] {
+			m.log.Debug("Generating required resource", "kind", framework.RoleBindingKind)
+			resources = append(resources, defaultRoleBinding(m.nexus))
 		}
 	}
+
 	return resources, nil
 }
 
+// GetDeployedResources returns the security resources deployed on the cluster
+func (m *Manager) GetDeployedResources() ([]resource.KubernetesResource, error) {
+	return framework.FetchDeployedResources(m.managedObjectsRef, m.nexus, m.client)
+}
+
 // GetCustomComparator returns the custom comp function used to compare a security resource.
 // Returns nil if there is none
 func (m *Manager) GetCustomComparator(t reflect.Type) func(deployed resource.KubernetesResource, requested resource.KubernetesResource) bool {
diff --git a/controllers/nexus/resource/security/manager_test.go b/controllers/nexus/resource/security/manager_test.go
index 08087dd5..2716dcf8 100644
--- a/controllers/nexus/resource/security/manager_test.go
+++ b/controllers/nexus/resource/security/manager_test.go
@@ -28,7 +28,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	"github.com/m88i/nexus-operator/api/v1alpha1"
-	"github.com/m88i/nexus-operator/pkg/framework"
 	"github.com/m88i/nexus-operator/pkg/test"
 )
 
@@ -43,7 +42,8 @@ func TestNewManager(t *testing.T) {
 		nexus:  nexus,
 		client: client,
 	}
-	got := NewManager(nexus, client)
+	got, err := NewManager(nexus, client, client)
+	assert.Nil(t, err)
 	assert.Equal(t, want.nexus, got.nexus)
 	assert.Equal(t, want.client, got.client)
 }
@@ -57,7 +57,7 @@ func TestManager_GetRequiredResources(t *testing.T) {
 		log:    logger.GetLoggerWithResource("test", baseNexus),
 	}
 
-	// the default service accout is _always_ created
+	// the default service account is _always_ created
 	// even if the user specified a different one
 	resources, err := mgr.GetRequiredResources()
 	assert.Nil(t, err)
@@ -69,10 +69,8 @@ func TestManager_GetRequiredResources(t *testing.T) {
 func TestManager_GetDeployedResources(t *testing.T) {
 	// first with no deployed resources
 	fakeClient := test.NewFakeClientBuilder().Build()
-	mgr := &Manager{
-		nexus:  baseNexus,
-		client: fakeClient,
-	}
+	mgr, _ := NewManager(baseNexus, fakeClient, fakeClient)
+
 	resources, err := mgr.GetDeployedResources()
 	assert.Nil(t, resources)
 	assert.Len(t, resources, 0)
@@ -95,24 +93,6 @@ func TestManager_GetDeployedResources(t *testing.T) {
 	assert.Contains(t, err.Error(), mockErrorMsg)
 }
 
-func TestManager_getDeployedSvcAccnt(t *testing.T) {
-	mgr := &Manager{
-		nexus:  baseNexus,
-		client: test.NewFakeClientBuilder().Build(),
-	}
-
-	// first, test without creating the svcAccnt
-	err := framework.Fetch(mgr.client, framework.Key(mgr.nexus), managedObjectsRef[framework.SvcAccountKind], framework.SvcAccountKind)
-	assert.True(t, errors.IsNotFound(err))
-
-	// now test after creating the svcAccnt
-	svcAccnt := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: mgr.nexus.Name, Namespace: mgr.nexus.Namespace}}
-	assert.NoError(t, mgr.client.Create(ctx.TODO(), svcAccnt))
-	err = framework.Fetch(mgr.client, framework.Key(svcAccnt), svcAccnt, framework.SvcAccountKind)
-	assert.NotNil(t, svcAccnt)
-	assert.NoError(t, err)
-}
-
 func TestManager_GetCustomComparator(t *testing.T) {
 	// the nexus and the client should have no effect on the
 	// comparator functions offered by the manager
diff --git a/controllers/nexus/resource/security/role_binding.go b/controllers/nexus/resource/security/role_binding.go
new file mode 100644
index 00000000..6f72b141
--- /dev/null
+++ b/controllers/nexus/resource/security/role_binding.go
@@ -0,0 +1,42 @@
+// Copyright 2020 Nexus Operator and/or its authors
+//
+// 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 security
+
+import (
+	rbacv1 "k8s.io/api/rbac/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/m88i/nexus-operator/api/v1alpha1"
+)
+
+const clusterRoleKind = "ClusterRole"
+
+func defaultRoleBinding(nexus *v1alpha1.Nexus) *rbacv1.RoleBinding {
+	return &rbacv1.RoleBinding{
+		ObjectMeta: metav1.ObjectMeta{Name: "community-nexus-uid-200"},
+		Subjects: []rbacv1.Subject{
+			{
+				Name:      nexus.Spec.ServiceAccountName,
+				Namespace: nexus.Namespace,
+				Kind:      rbacv1.ServiceAccountKind,
+			},
+		},
+		RoleRef: rbacv1.RoleRef{
+			APIGroup: rbacv1.GroupName,
+			Kind:     clusterRoleKind,
+			Name:     clusterRoleName,
+		},
+	}
+}
diff --git a/controllers/nexus/resource/security/scc.go b/controllers/nexus/resource/security/scc.go
new file mode 100644
index 00000000..bcc57ba2
--- /dev/null
+++ b/controllers/nexus/resource/security/scc.go
@@ -0,0 +1,50 @@
+// Copyright 2020 Nexus Operator and/or its authors
+//
+// 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 security
+
+import (
+	secv1 "github.com/openshift/api/security/v1"
+	controllerruntime "sigs.k8s.io/controller-runtime"
+)
+
+var (
+	communityUID = int64(200)
+	sccName      = "allow-nexus-userid-200"
+)
+
+func defaultSCC() *secv1.SecurityContextConstraints {
+	return &secv1.SecurityContextConstraints{
+		ObjectMeta: controllerruntime.ObjectMeta{Name: sccName},
+		FSGroup: secv1.FSGroupStrategyOptions{
+			Type:   secv1.FSGroupStrategyMustRunAs,
+			Ranges: []secv1.IDRange{{Min: communityUID, Max: communityUID}},
+		},
+		RunAsUser: secv1.RunAsUserStrategyOptions{
+			Type: secv1.RunAsUserStrategyMustRunAs,
+			UID:  &communityUID,
+		},
+		SELinuxContext: secv1.SELinuxContextStrategyOptions{
+			Type: secv1.SELinuxStrategyMustRunAs,
+		},
+		SupplementalGroups: secv1.SupplementalGroupsStrategyOptions{
+			Type:   secv1.SupplementalGroupsStrategyMustRunAs,
+			Ranges: []secv1.IDRange{{Min: communityUID, Max: communityUID}},
+		},
+		Volumes: []secv1.FSType{
+			secv1.FSTypePersistentVolumeClaim,
+			secv1.FSTypeSecret,
+		},
+	}
+}
diff --git a/controllers/nexus_controller.go b/controllers/nexus_controller.go
index 311e719c..7f6a4564 100644
--- a/controllers/nexus_controller.go
+++ b/controllers/nexus_controller.go
@@ -71,6 +71,8 @@ type NexusReconciler struct {
 // +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;create
 // +kubebuilder:rbac:groups=route.openshift.io,resources=routes,verbs=create;delete;get;list;patch;update;watch
 // +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=create;delete;get;list;patch;update;watch
+// +kubebuilder:rbac:groups=security.openshift.io,resources=scc,verbs=create;get;update;watch
+// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrole;rolebinding,verbs=create;get;update;watch
 
 func (r *NexusReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
 	_ = context.Background()
diff --git a/nexus-operator.yaml b/nexus-operator.yaml
index 931d7b72..99207b07 100644
--- a/nexus-operator.yaml
+++ b/nexus-operator.yaml
@@ -476,6 +476,16 @@ rules:
   - patch
   - update
   - watch
+- apiGroups:
+  - rbac.authorization.k8s.io
+  resources:
+  - clusterrole
+  - rolebinding
+  verbs:
+  - create
+  - get
+  - update
+  - watch
 - apiGroups:
   - route.openshift.io
   resources:
@@ -488,6 +498,15 @@ rules:
   - patch
   - update
   - watch
+- apiGroups:
+  - security.openshift.io
+  resources:
+  - scc
+  verbs:
+  - create
+  - get
+  - update
+  - watch
 ---
 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
diff --git a/pkg/framework/fetcher.go b/pkg/framework/fetcher.go
index 58e4b421..14ddb0f6 100644
--- a/pkg/framework/fetcher.go
+++ b/pkg/framework/fetcher.go
@@ -16,13 +16,30 @@ package framework
 
 import (
 	ctx "context"
+	"fmt"
 
 	"github.com/RHsyseng/operator-utils/pkg/resource"
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/m88i/nexus-operator/api/v1alpha1"
 )
 
+// FetchDeployedResources fetches deployed resources whose Kind is present in "managedObjectsRef"
+func FetchDeployedResources(managedObjectsRef map[string]resource.KubernetesResource, nexus *v1alpha1.Nexus, cli client.Client) ([]resource.KubernetesResource, error) {
+	var resources []resource.KubernetesResource
+	for resType, resRef := range managedObjectsRef {
+		if err := Fetch(cli, Key(nexus), resRef, resType); err == nil {
+			resources = append(resources, resRef)
+		} else if !errors.IsNotFound(err) {
+			return nil, fmt.Errorf("could not fetch %s (%s/%s): %v", resType, nexus.Namespace, nexus.Name, err)
+		}
+	}
+	return resources, nil
+}
+
+// Fetch fetches a single deployed resource and stores it in "instance"
 func Fetch(client client.Client, key types.NamespacedName, instance resource.KubernetesResource, kind string) error {
 	log.Info("Attempting to fetch deployed resource", "kind", kind, "namespacedName", key)
 	if err := client.Get(ctx.TODO(), key, instance); err != nil {
diff --git a/pkg/framework/kinds.go b/pkg/framework/kinds.go
index d3b4bc10..d585cbee 100644
--- a/pkg/framework/kinds.go
+++ b/pkg/framework/kinds.go
@@ -15,11 +15,14 @@
 package framework
 
 const (
-	DeploymentKind = "Deployment"
-	IngressKind    = "Ingress"
-	PVCKind        = "Persistent Volume Claim"
-	RouteKind      = "Route"
-	SecretKind     = "Secret"
-	ServiceKind    = "Service"
-	SvcAccountKind = "Service Account"
+	ClusterRoleKind = "Cluster Role"
+	DeploymentKind  = "Deployment"
+	IngressKind     = "Ingress"
+	PVCKind         = "Persistent Volume Claim"
+	RoleBindingKind = "Role Binding"
+	RouteKind       = "Route"
+	SecretKind      = "Secret"
+	ServiceKind     = "Service"
+	SCCKind         = "Security Context Constraint"
+	SvcAccountKind  = "Service Account"
 )

From 7df54e10a5631958614002eee61af56250969e22 Mon Sep 17 00:00:00 2001
From: Lucas Caparelli <lucas.caparelli112@gmail.com>
Date: Fri, 23 Oct 2020 19:49:44 -0300
Subject: [PATCH 2/3] Adjust discovery usage

Signed-off-by: Lucas Caparelli <lucas.caparelli112@gmail.com>
---
 controllers/nexus/resource/networking/manager_test.go | 3 ++-
 controllers/nexus/resource/security/manager.go        | 7 +++----
 controllers/nexus/resource/security/manager_test.go   | 6 ++++--
 3 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/controllers/nexus/resource/networking/manager_test.go b/controllers/nexus/resource/networking/manager_test.go
index e9ef8bda..30990368 100644
--- a/controllers/nexus/resource/networking/manager_test.go
+++ b/controllers/nexus/resource/networking/manager_test.go
@@ -211,7 +211,8 @@ func TestManager_createIngress(t *testing.T) {
 func TestManager_GetDeployedResources(t *testing.T) {
 	// first with no deployed resources
 	fakeClient := test.NewFakeClientBuilder().WithIngress().OnOpenshift().Build()
-	mgr, _ := NewManager(nodePortNexus, fakeClient, fakeClient)
+	discovery.SetClient(fakeClient)
+	mgr, _ := NewManager(nodePortNexus, fakeClient)
 
 	resources, err := mgr.GetDeployedResources()
 	assert.Nil(t, resources)
diff --git a/controllers/nexus/resource/security/manager.go b/controllers/nexus/resource/security/manager.go
index e0643beb..80d85ef5 100644
--- a/controllers/nexus/resource/security/manager.go
+++ b/controllers/nexus/resource/security/manager.go
@@ -23,12 +23,11 @@ import (
 	secv1 "github.com/openshift/api/security/v1"
 	core "k8s.io/api/core/v1"
 	rbacv1 "k8s.io/api/rbac/v1"
-	"k8s.io/client-go/discovery"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/m88i/nexus-operator/api/v1alpha1"
 	"github.com/m88i/nexus-operator/controllers/nexus/resource/validation"
-	"github.com/m88i/nexus-operator/pkg/cluster/openshift"
+	"github.com/m88i/nexus-operator/pkg/cluster/discovery"
 	"github.com/m88i/nexus-operator/pkg/framework"
 	"github.com/m88i/nexus-operator/pkg/logger"
 )
@@ -44,7 +43,7 @@ type Manager struct {
 }
 
 // NewManager creates a security resources Manager
-func NewManager(nexus *v1alpha1.Nexus, client client.Client, disc discovery.DiscoveryInterface) (*Manager, error) {
+func NewManager(nexus *v1alpha1.Nexus, client client.Client) (*Manager, error) {
 	mgr := &Manager{
 		nexus:  nexus,
 		client: client,
@@ -56,7 +55,7 @@ func NewManager(nexus *v1alpha1.Nexus, client client.Client, disc discovery.Disc
 		},
 	}
 
-	isOCP, err := openshift.IsOpenShift(disc)
+	isOCP, err := discovery.IsOpenShift()
 	if err != nil {
 		return nil, fmt.Errorf("unable to determine if on Openshift: %v", err)
 	}
diff --git a/controllers/nexus/resource/security/manager_test.go b/controllers/nexus/resource/security/manager_test.go
index 2716dcf8..8473268e 100644
--- a/controllers/nexus/resource/security/manager_test.go
+++ b/controllers/nexus/resource/security/manager_test.go
@@ -20,6 +20,7 @@ import (
 	"reflect"
 	"testing"
 
+	"github.com/m88i/nexus-operator/pkg/cluster/discovery"
 	"github.com/m88i/nexus-operator/pkg/logger"
 
 	"github.com/stretchr/testify/assert"
@@ -38,11 +39,12 @@ func TestNewManager(t *testing.T) {
 	// so here we just check if the resulting manager took in the arguments correctly
 	nexus := baseNexus
 	client := test.NewFakeClientBuilder().Build()
+	discovery.SetClient(client)
 	want := &Manager{
 		nexus:  nexus,
 		client: client,
 	}
-	got, err := NewManager(nexus, client, client)
+	got, err := NewManager(nexus, client)
 	assert.Nil(t, err)
 	assert.Equal(t, want.nexus, got.nexus)
 	assert.Equal(t, want.client, got.client)
@@ -69,7 +71,7 @@ func TestManager_GetRequiredResources(t *testing.T) {
 func TestManager_GetDeployedResources(t *testing.T) {
 	// first with no deployed resources
 	fakeClient := test.NewFakeClientBuilder().Build()
-	mgr, _ := NewManager(baseNexus, fakeClient, fakeClient)
+	mgr, _ := NewManager(baseNexus, fakeClient)
 
 	resources, err := mgr.GetDeployedResources()
 	assert.Nil(t, resources)

From 5b13e629bd06c82546918d2331216756630d5e51 Mon Sep 17 00:00:00 2001
From: Lucas Caparelli <lucas.caparelli112@gmail.com>
Date: Fri, 23 Oct 2020 20:19:19 -0300
Subject: [PATCH 3/3] Register security API and fix role permissions

Signed-off-by: Lucas Caparelli <lucas.caparelli112@gmail.com>
---
 bundle/manifests/nexus-operator.clusterserviceversion.yaml | 2 ++
 config/rbac/role.yaml                                      | 2 ++
 controllers/nexus_controller.go                            | 4 ++--
 main.go                                                    | 6 ++++--
 nexus-operator.yaml                                        | 2 ++
 5 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/bundle/manifests/nexus-operator.clusterserviceversion.yaml b/bundle/manifests/nexus-operator.clusterserviceversion.yaml
index b4d13ef7..ad8c9c7f 100644
--- a/bundle/manifests/nexus-operator.clusterserviceversion.yaml
+++ b/bundle/manifests/nexus-operator.clusterserviceversion.yaml
@@ -175,6 +175,7 @@ spec:
           verbs:
           - create
           - get
+          - list
           - update
           - watch
         - apiGroups:
@@ -196,6 +197,7 @@ spec:
           verbs:
           - create
           - get
+          - list
           - update
           - watch
         - apiGroups:
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index 12445393..a2917bd8 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -106,6 +106,7 @@ rules:
   verbs:
   - create
   - get
+  - list
   - update
   - watch
 - apiGroups:
@@ -127,5 +128,6 @@ rules:
   verbs:
   - create
   - get
+  - list
   - update
   - watch
diff --git a/controllers/nexus_controller.go b/controllers/nexus_controller.go
index 7f6a4564..bb3ef543 100644
--- a/controllers/nexus_controller.go
+++ b/controllers/nexus_controller.go
@@ -71,8 +71,8 @@ type NexusReconciler struct {
 // +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;create
 // +kubebuilder:rbac:groups=route.openshift.io,resources=routes,verbs=create;delete;get;list;patch;update;watch
 // +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=create;delete;get;list;patch;update;watch
-// +kubebuilder:rbac:groups=security.openshift.io,resources=scc,verbs=create;get;update;watch
-// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrole;rolebinding,verbs=create;get;update;watch
+// +kubebuilder:rbac:groups=security.openshift.io,resources=scc,verbs=create;get;list;update;watch
+// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrole;rolebinding,verbs=create;get;list;update;watch
 
 func (r *NexusReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
 	_ = context.Background()
diff --git a/main.go b/main.go
index f395a244..92162430 100644
--- a/main.go
+++ b/main.go
@@ -23,6 +23,7 @@ import (
 	"strings"
 
 	routev1 "github.com/openshift/api/route/v1"
+	secv1 "github.com/openshift/api/security/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 	k8sdisc "k8s.io/client-go/discovery"
@@ -45,8 +46,9 @@ var (
 )
 
 func init() {
-	// adding routev1
-	utilruntime.Must(routev1.AddToScheme(scheme))
+	// adding routev1 (routes) and secv1 (SCCs)
+	utilruntime.Must(routev1.Install(scheme))
+	utilruntime.Must(secv1.Install(scheme))
 	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
 	utilruntime.Must(appsv1alpha1.AddToScheme(scheme))
 	// +kubebuilder:scaffold:scheme
diff --git a/nexus-operator.yaml b/nexus-operator.yaml
index 99207b07..63a2e89d 100644
--- a/nexus-operator.yaml
+++ b/nexus-operator.yaml
@@ -484,6 +484,7 @@ rules:
   verbs:
   - create
   - get
+  - list
   - update
   - watch
 - apiGroups:
@@ -505,6 +506,7 @@ rules:
   verbs:
   - create
   - get
+  - list
   - update
   - watch
 ---