diff --git a/Makefile b/Makefile index 4aca5032..1b230b44 100644 --- a/Makefile +++ b/Makefile @@ -142,7 +142,6 @@ manifests: controller-gen .PHONY: import-manifests import-manifests: kustomize $(KUSTOMIZE) build github.com/open-policy-agent/gatekeeper/config/default/?ref=$(GATEKEEPER_VERSION) -o $(GATEKEEPER_MANIFEST_DIR) - rm -f ./$(GATEKEEPER_MANIFEST_DIR)/v1_namespace_gatekeeper-system.yaml # Run go fmt against code .PHONY: fmt diff --git a/README.md b/README.md index a413954d..42a318a3 100644 --- a/README.md +++ b/README.md @@ -29,18 +29,11 @@ Then proceed to the installation method you prefer below. ### Outside the Cluster -If you would like to run the Operator outside the cluster, you'll have to set the -`WATCH_NAMESPACE` environment variable to the namespace you want the -Operator to monitor: +If you would like to run the Operator outside the cluster you just execute: -1. Set the WATCH_NAMESPACE environment variable: - ```shell - export WATCH_NAMESPACE=gatekeeper-system - ``` -1. You then run the Operator with: - ```shell - make run - ``` +```shell +make run +``` ### Inside the Cluster diff --git a/bundle/manifests/gatekeeper-operator.clusterserviceversion.yaml b/bundle/manifests/gatekeeper-operator.clusterserviceversion.yaml index 1656eede..77cbdf5d 100644 --- a/bundle/manifests/gatekeeper-operator.clusterserviceversion.yaml +++ b/bundle/manifests/gatekeeper-operator.clusterserviceversion.yaml @@ -205,6 +205,18 @@ spec: - get - patch - update + - apiGroups: + - "" + resources: + - namespaces + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - security.openshift.io resourceNames: @@ -256,11 +268,6 @@ spec: - --enable-leader-election command: - /manager - env: - - name: WATCH_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.annotations['olm.targetNamespaces'] image: quay.io/gatekeeper/gatekeeper-operator:latest imagePullPolicy: Always name: manager diff --git a/config/gatekeeper/v1_namespace_gatekeeper-system.yaml b/config/gatekeeper/v1_namespace_gatekeeper-system.yaml new file mode 100644 index 00000000..0369e302 --- /dev/null +++ b/config/gatekeeper/v1_namespace_gatekeeper-system.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + admission.gatekeeper.sh/ignore: no-self-managing + control-plane: controller-manager + gatekeeper.sh/system: "yes" + name: gatekeeper-system diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 1b1bbd3d..b151b7f5 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -37,9 +37,4 @@ spec: requests: cpu: 100m memory: 20Mi - env: - - name: WATCH_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace terminationGracePeriodSeconds: 10 diff --git a/config/rbac/overlays/openshift/kustomization.yaml b/config/rbac/overlays/openshift/kustomization.yaml index 17862421..4b948830 100644 --- a/config/rbac/overlays/openshift/kustomization.yaml +++ b/config/rbac/overlays/openshift/kustomization.yaml @@ -7,6 +7,21 @@ patchesJSON6902: kind: ClusterRole name: manager-role patch: |- + - op: add + path: /rules/- + value: + apiGroups: + - "" + resources: + - namespaces + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - op: add path: /rules/- value: diff --git a/controllers/gatekeeper_controller.go b/controllers/gatekeeper_controller.go index 88195740..1da3921c 100644 --- a/controllers/gatekeeper_controller.go +++ b/controllers/gatekeeper_controller.go @@ -22,7 +22,6 @@ import ( "strconv" "time" - "github.com/RHsyseng/operator-utils/pkg/utils/openshift" "github.com/go-logr/logr" "github.com/openshift/library-go/pkg/manifest" "github.com/pkg/errors" @@ -35,7 +34,6 @@ import ( "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -47,6 +45,7 @@ import ( var ( defaultGatekeeperCrName = "gatekeeper" openshiftAssetsDir = "openshift/" + NamespaceFile = "v1_namespace_gatekeeper-system.yaml" RoleFile = "rbac.authorization.k8s.io_v1_role_gatekeeper-manager-role.yaml" AuditFile = "apps_v1_deployment_gatekeeper-audit.yaml" WebhookFile = "apps_v1_deployment_gatekeeper-controller-manager.yaml" @@ -55,6 +54,7 @@ var ( ServerCertFile = "v1_secret_gatekeeper-webhook-server-cert.yaml" ValidatingWebhookConfiguration = "admissionregistration.k8s.io_v1beta1_validatingwebhookconfiguration_gatekeeper-validating-webhook-configuration.yaml" orderedStaticAssets = []string{ + NamespaceFile, "apiextensions.k8s.io_v1beta1_customresourcedefinition_configs.config.gatekeeper.sh.yaml", "apiextensions.k8s.io_v1beta1_customresourcedefinition_constrainttemplates.templates.gatekeeper.sh.yaml", "apiextensions.k8s.io_v1beta1_customresourcedefinition_constrainttemplatepodstatuses.status.gatekeeper.sh.yaml", @@ -89,9 +89,10 @@ const ( // GatekeeperReconciler reconciles a Gatekeeper object type GatekeeperReconciler struct { client.Client - Log logr.Logger - Scheme *runtime.Scheme - Namespace string + Log logr.Logger + Scheme *runtime.Scheme + Namespace string + PlatformName util.PlatformType } // Gatekeeper Operator RBAC permissions to manager Gatekeeper custom resource @@ -127,15 +128,6 @@ func (r *GatekeeperReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) logger := r.Log.WithValues("gatekeeper", req.NamespacedName) logger.Info("Reconciling Gatekeeper") - cfg, err := config.GetConfig() - if err != nil { - return ctrl.Result{}, err - } - platformName, err := openshift.GetPlatformName(cfg) - if err != nil { - return ctrl.Result{}, err - } - if req.Name != defaultGatekeeperCrName { err := fmt.Errorf("Gatekeeper resource name must be '%s'", defaultGatekeeperCrName) logger.Error(err, "Invalid Gatekeeper resource name") @@ -144,7 +136,7 @@ func (r *GatekeeperReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) } gatekeeper := &operatorv1alpha1.Gatekeeper{} - err = r.Get(ctx, req.NamespacedName, gatekeeper) + err := r.Get(ctx, req.NamespacedName, gatekeeper) if err != nil { if apierrors.IsNotFound(err) { @@ -154,7 +146,7 @@ func (r *GatekeeperReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) return ctrl.Result{}, err } - err = r.deployGatekeeperResources(gatekeeper, platformName) + err = r.deployGatekeeperResources(gatekeeper) if err != nil { return ctrl.Result{}, errors.Wrap(err, "Unable to deploy Gatekeeper resources") } @@ -180,16 +172,24 @@ func (r *GatekeeperReconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -func (r *GatekeeperReconciler) deployGatekeeperResources(gatekeeper *operatorv1alpha1.Gatekeeper, platformName string) error { +func (r *GatekeeperReconciler) deployGatekeeperResources(gatekeeper *operatorv1alpha1.Gatekeeper) error { for _, a := range getStaticAssets(gatekeeper) { - if a == RoleFile && platformName == "OpenShift" { + // Handle special cases in switch below. + switch { + case a == NamespaceFile && !r.isOpenShift(): + // Ignore the namespace resource on Kubernetes as we default to use + // the same namespace as the operator, which by definition is + // already created as a result of executing this code. + continue + case a == RoleFile && r.isOpenShift(): a = openshiftAssetsDir + a } + manifest, err := util.GetManifest(a) if err != nil { return err } - if err = crOverrides(gatekeeper, a, manifest, r.Namespace, (platformName == "OpenShift")); err != nil { + if err = crOverrides(gatekeeper, a, manifest, r.Namespace, r.isOpenShift()); err != nil { return err } @@ -262,6 +262,10 @@ func (r *GatekeeperReconciler) updateOrCreateResource(manifest *manifest.Manifes return err } +func (r *GatekeeperReconciler) isOpenShift() bool { + return util.IsOpenShift(r.PlatformName) +} + var commonSpecOverridesFn = []func(*unstructured.Unstructured, operatorv1alpha1.GatekeeperSpec) error{ setAffinity, setNodeSelector, @@ -275,8 +279,12 @@ var commonContainerOverridesFn = []func(map[string]interface{}, operatorv1alpha1 // crOverrides func crOverrides(gatekeeper *operatorv1alpha1.Gatekeeper, asset string, manifest *manifest.Manifest, namespace string, isOpenshift bool) error { - // set current namespace - if err := setCurrentNamespace(manifest.Obj, asset, namespace); err != nil { + if asset == NamespaceFile { + manifest.Obj.SetName(namespace) + return nil + } + // set resource's namespace + if err := setNamespace(manifest.Obj, asset, namespace); err != nil { return err } // audit overrides @@ -597,7 +605,7 @@ func setContainerArg(obj *unstructured.Unstructured, containerName, argName stri }) } -func setCurrentNamespace(obj *unstructured.Unstructured, asset, namespace string) error { +func setNamespace(obj *unstructured.Unstructured, asset, namespace string) error { if obj.GetNamespace() != "" { obj.SetNamespace(namespace) } diff --git a/main.go b/main.go index a0a6fdd8..93ad99e6 100644 --- a/main.go +++ b/main.go @@ -18,7 +18,6 @@ package main import ( "flag" - "fmt" "os" "k8s.io/apimachinery/pkg/runtime" @@ -28,8 +27,11 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "github.com/RHsyseng/operator-utils/pkg/utils/openshift" operatorv1alpha1 "github.com/gatekeeper/gatekeeper-operator/api/v1alpha1" "github.com/gatekeeper/gatekeeper-operator/controllers" + "github.com/gatekeeper/gatekeeper-operator/pkg/util" + "github.com/pkg/errors" // +kubebuilder:scaffold:imports ) @@ -56,30 +58,37 @@ func main() { ctrl.SetLogger(zap.New(zap.UseDevMode(true))) - watchNamespace, err := getWatchNamespace() - if err != nil { - setupLog.Error(err, "unable to get WatchNamespace, ") - os.Exit(1) - } - - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + cfg := ctrl.GetConfigOrDie() + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ Scheme: scheme, MetricsBindAddress: metricsAddr, Port: 9443, LeaderElection: enableLeaderElection, LeaderElectionID: "6ccdc528.gatekeeper.sh", - Namespace: watchNamespace, // namespaced-scope when the value is not an empty string }) if err != nil { setupLog.Error(err, "unable to start manager") os.Exit(1) } + platformName, err := openshift.GetPlatformName(cfg) + if err != nil { + setupLog.Error(err, "unable to get platform name") + os.Exit(1) + } + + namespace, err := gatekeeperNamespace(platformName) + if err != nil { + setupLog.Error(err, "unable to get Gatekeeper namespace") + os.Exit(1) + } + if err = (&controllers.GatekeeperReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Gatekeeper"), - Scheme: mgr.GetScheme(), - Namespace: watchNamespace, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("Gatekeeper"), + Scheme: mgr.GetScheme(), + Namespace: namespace, + PlatformName: util.PlatformType(platformName), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Gatekeeper") os.Exit(1) @@ -93,17 +102,18 @@ func main() { } } -// getWatchNamespace returns the Namespace the operator should be watching for -// changes. -func getWatchNamespace() (string, error) { - // WatchNamespaceEnvVar is the constant for env variable WATCH_NAMESPACE - // which specifies the Namespace to watch. An empty value means the - // operator is running with cluster scope. - var watchNamespaceEnvVar = "WATCH_NAMESPACE" - - ns, found := os.LookupEnv(watchNamespaceEnvVar) - if !found { - return "", fmt.Errorf("%s must be set", watchNamespaceEnvVar) +func gatekeeperNamespace(platformName string) (string, error) { + ns, err := util.GetOperatorNamespace() + if err != nil { + return "", errors.Wrapf(err, "Failed to get operator namespace") + } + + switch util.PlatformType(platformName) { + case util.OpenShift: + return util.GetPlatformNamespace(platformName), nil + case util.Kubernetes: + fallthrough + default: + return ns, nil } - return ns, nil } diff --git a/pkg/bindata/bindata.go b/pkg/bindata/bindata.go index 3bbbfca5..54562960 100644 --- a/pkg/bindata/bindata.go +++ b/pkg/bindata/bindata.go @@ -13,6 +13,7 @@ // config/gatekeeper/rbac.authorization.k8s.io_v1_clusterrolebinding_gatekeeper-manager-rolebinding.yaml // config/gatekeeper/rbac.authorization.k8s.io_v1_role_gatekeeper-manager-role.yaml // config/gatekeeper/rbac.authorization.k8s.io_v1_rolebinding_gatekeeper-manager-rolebinding.yaml +// config/gatekeeper/v1_namespace_gatekeeper-system.yaml // config/gatekeeper/v1_secret_gatekeeper-webhook-server-cert.yaml // config/gatekeeper/v1_service_gatekeeper-webhook-service.yaml // config/gatekeeper/v1_serviceaccount_gatekeeper-admin.yaml @@ -1132,6 +1133,31 @@ func configGatekeeperRbacAuthorizationK8sIo_v1_rolebinding_gatekeeperManagerRole return a, nil } +var _configGatekeeperV1_namespace_gatekeeperSystemYaml = []byte(`apiVersion: v1 +kind: Namespace +metadata: + labels: + admission.gatekeeper.sh/ignore: no-self-managing + control-plane: controller-manager + gatekeeper.sh/system: "yes" + name: gatekeeper-system +`) + +func configGatekeeperV1_namespace_gatekeeperSystemYamlBytes() ([]byte, error) { + return _configGatekeeperV1_namespace_gatekeeperSystemYaml, nil +} + +func configGatekeeperV1_namespace_gatekeeperSystemYaml() (*asset, error) { + bytes, err := configGatekeeperV1_namespace_gatekeeperSystemYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "config/gatekeeper/v1_namespace_gatekeeper-system.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + var _configGatekeeperV1_secret_gatekeeperWebhookServerCertYaml = []byte(`apiVersion: v1 kind: Secret metadata: @@ -1277,6 +1303,7 @@ var _bindata = map[string]func() (*asset, error){ "config/gatekeeper/rbac.authorization.k8s.io_v1_clusterrolebinding_gatekeeper-manager-rolebinding.yaml": configGatekeeperRbacAuthorizationK8sIo_v1_clusterrolebinding_gatekeeperManagerRolebindingYaml, "config/gatekeeper/rbac.authorization.k8s.io_v1_role_gatekeeper-manager-role.yaml": configGatekeeperRbacAuthorizationK8sIo_v1_role_gatekeeperManagerRoleYaml, "config/gatekeeper/rbac.authorization.k8s.io_v1_rolebinding_gatekeeper-manager-rolebinding.yaml": configGatekeeperRbacAuthorizationK8sIo_v1_rolebinding_gatekeeperManagerRolebindingYaml, + "config/gatekeeper/v1_namespace_gatekeeper-system.yaml": configGatekeeperV1_namespace_gatekeeperSystemYaml, "config/gatekeeper/v1_secret_gatekeeper-webhook-server-cert.yaml": configGatekeeperV1_secret_gatekeeperWebhookServerCertYaml, "config/gatekeeper/v1_service_gatekeeper-webhook-service.yaml": configGatekeeperV1_service_gatekeeperWebhookServiceYaml, "config/gatekeeper/v1_serviceaccount_gatekeeper-admin.yaml": configGatekeeperV1_serviceaccount_gatekeeperAdminYaml, @@ -1340,6 +1367,7 @@ var _bintree = &bintree{nil, map[string]*bintree{ "rbac.authorization.k8s.io_v1_clusterrolebinding_gatekeeper-manager-rolebinding.yaml": {configGatekeeperRbacAuthorizationK8sIo_v1_clusterrolebinding_gatekeeperManagerRolebindingYaml, map[string]*bintree{}}, "rbac.authorization.k8s.io_v1_role_gatekeeper-manager-role.yaml": {configGatekeeperRbacAuthorizationK8sIo_v1_role_gatekeeperManagerRoleYaml, map[string]*bintree{}}, "rbac.authorization.k8s.io_v1_rolebinding_gatekeeper-manager-rolebinding.yaml": {configGatekeeperRbacAuthorizationK8sIo_v1_rolebinding_gatekeeperManagerRolebindingYaml, map[string]*bintree{}}, + "v1_namespace_gatekeeper-system.yaml": {configGatekeeperV1_namespace_gatekeeperSystemYaml, map[string]*bintree{}}, "v1_secret_gatekeeper-webhook-server-cert.yaml": {configGatekeeperV1_secret_gatekeeperWebhookServerCertYaml, map[string]*bintree{}}, "v1_service_gatekeeper-webhook-service.yaml": {configGatekeeperV1_service_gatekeeperWebhookServiceYaml, map[string]*bintree{}}, "v1_serviceaccount_gatekeeper-admin.yaml": {configGatekeeperV1_serviceaccount_gatekeeperAdminYaml, map[string]*bintree{}}, diff --git a/pkg/util/namespace.go b/pkg/util/namespace.go new file mode 100644 index 00000000..751f3d8b --- /dev/null +++ b/pkg/util/namespace.go @@ -0,0 +1,52 @@ +/* +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 util + +import ( + "io/ioutil" + "os" + "strings" + + "github.com/pkg/errors" +) + +var ( + DefaultGatekeeperNamespace = "gatekeeper-system" + DefaultOpenShiftGatekeeperNamespace = "openshift-gatekeeper-system" +) + +// GetOperatorNamespace returns the namespace the operator is running in from +// the associated service account secret. +func GetOperatorNamespace() (string, error) { + nsBytes, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") + if err != nil { + if os.IsNotExist(err) { + return "", errors.New("namespace not found for current environment") + } + return "", err + } + ns := strings.TrimSpace(string(nsBytes)) + return ns, nil +} + +// GetPlatformNamespace returns the namespace for the designated platform. +func GetPlatformNamespace(platformName string) string { + switch PlatformType(platformName) { + case OpenShift: + return DefaultOpenShiftGatekeeperNamespace + default: + return DefaultGatekeeperNamespace + } +} diff --git a/pkg/util/platform.go b/pkg/util/platform.go new file mode 100644 index 00000000..ee7739ee --- /dev/null +++ b/pkg/util/platform.go @@ -0,0 +1,26 @@ +/* +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 util + +type PlatformType string + +const ( + OpenShift PlatformType = "OpenShift" + Kubernetes PlatformType = "Kubernetes" +) + +func IsOpenShift(platformName PlatformType) bool { + return platformName == OpenShift +}