diff --git a/bundle/manifests/nexus-operator.clusterserviceversion.yaml b/bundle/manifests/nexus-operator.clusterserviceversion.yaml index b98ff23e..ad8c9c7f 100644 --- a/bundle/manifests/nexus-operator.clusterserviceversion.yaml +++ b/bundle/manifests/nexus-operator.clusterserviceversion.yaml @@ -167,6 +167,17 @@ spec: - patch - update - watch + - apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrole + - rolebinding + verbs: + - create + - get + - list + - update + - watch - apiGroups: - route.openshift.io resources: @@ -179,6 +190,16 @@ spec: - patch - update - watch + - apiGroups: + - security.openshift.io + resources: + - scc + verbs: + - create + - get + - list + - update + - watch - apiGroups: - authentication.k8s.io resources: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 92cb9563..a2917bd8 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -98,6 +98,17 @@ rules: - patch - update - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrole + - rolebinding + verbs: + - create + - get + - list + - update + - watch - apiGroups: - route.openshift.io resources: @@ -110,3 +121,13 @@ rules: - patch - update - watch +- apiGroups: + - security.openshift.io + resources: + - scc + verbs: + - create + - get + - list + - 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..30990368 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,9 @@ 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, - } + discovery.SetClient(fakeClient) + mgr, _ := NewManager(nodePortNexus, 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..80d85ef5 100644 --- a/controllers/nexus/resource/security/manager.go +++ b/controllers/nexus/resource/security/manager.go @@ -17,59 +17,87 @@ 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" "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/discovery" "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) (*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 := discovery.IsOpenShift() + 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..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" @@ -28,7 +29,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" ) @@ -39,11 +39,13 @@ 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 := NewManager(nexus, client) + got, err := NewManager(nexus, client) + assert.Nil(t, err) assert.Equal(t, want.nexus, got.nexus) assert.Equal(t, want.client, got.client) } @@ -57,7 +59,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 +71,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) @@ -95,24 +95,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..bb3ef543 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;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 931d7b72..63a2e89d 100644 --- a/nexus-operator.yaml +++ b/nexus-operator.yaml @@ -476,6 +476,17 @@ rules: - patch - update - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrole + - rolebinding + verbs: + - create + - get + - list + - update + - watch - apiGroups: - route.openshift.io resources: @@ -488,6 +499,16 @@ rules: - patch - update - watch +- apiGroups: + - security.openshift.io + resources: + - scc + verbs: + - create + - get + - list + - 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" )